Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2369 / 261 | Оценка: 4.47 / 4.24 | Длительность: 14:45:00
Лекция 6:

Интерфейсы. Множественное наследование

< Лекция 5 || Лекция 6: 12345 || Лекция 7 >

Итераторы

Нам удалось достаточно просто организовать перечислимость в классе за счет того, что в классе был определен контейнер со встроенным методом GetEnumerator. А что, если такого контейнера нет, или таких контейнеров несколько и хотелось бы организовать перечисление по каждому из контейнеров? Для решения подобных задач в язык C#, начиная с версии 2.0, встроен мощный механизм итераторов, существенно облегчающих задачу перечислимости объектов класса и открывающих новые возможности, которые нельзя реализовать только за счет наследования интерфейса IEnumerable.

Итератором называется метод класса, возвращающий в качестве результата интерфейсный объект типа IEnumerable. В теле итератора должен присутствовать оператор языка yield, имеющий следующий синтаксис:

yield return <выражение> ;

При выполнении итератора автоматически создается контейнер, в который добавляется объект при каждом выполнении оператора yield. Добавляемый в контейнер элемент определяется выражением оператора yield. Порядок выполнения операторов yield определяет порядок перечислимости элементов контейнера. Оператор yield может быть задан в форме

yield break;

В такой форме он сигнализирует об окончании заполнения контейнера элементами.

Итераторов в классе может быть несколько. Каждый из них создает свой вид перечислимости объектов класса. Итератору не требуется, чтобы в классе существовал контейнер со встроенным методом перечислимости, хотя итератор может использовать контейнер, изменяя порядок перечислимости, заданный для контейнера по умолчанию.

Давайте добавим в класс Persons два итератора. Первый из этих итераторов будет использовать существующий в классе контейнер, изменяя заданный порядок перечислимости элементов на обратный.

/// <summary>
 /// Перечисление элементов массива container
 /// в обратном порядке
 /// </summary>
 /// <returns>интерфейсный объект</returns>
 public IEnumerable ReverseIterator()
 {
     for (int i = container.Length - 1; i >= 0; i--)
         yield return container[i];
 }

Оператор yield, работающий в цикле, первым возвратит последний элемент массива, а последним - первый.

Создадим еще один итератор, никак не связанный с элементами массива поля container. Зададим перечисление, содержащее имена известных языков программирования:

public enum ProgrammingLanguages
    {
        Fortran, Algol, Cobol, Simula, Pascal,
        Ada, C, CPlusPlus, Eiffel, Java, CSharp
    }

Построим теперь итератор, в котором создадим локальную переменную для данного перечисления и используем ее, чтобы поместить в контейнер элементы перечисления.

/// <summary>
 /// Итератор. Создает объект перечисления
 /// и строит контейнер, 
 /// помещая в него элементы перечисления
 /// </summary>
 /// <returns>интерфейсный объект</returns>
 public IEnumerable LanguagesIterator()
 {
     ProgrammingLanguages lang;
     int i = 0;
     while (true)
     {
         lang = (ProgrammingLanguages)i;
  yield return lang;
  if (lang.ToString() == "CSharp") yield break;  
  i++;
     }
 }

В цикле оператор yield возвращает элементы перечисления до тех пор, пока не встретится элемент со значением "CSharp". Давайте теперь протестируем то, что у нас получилось. Добавим несколько строк кода в уже рассмотренный метод TestEnumeration:

Console.WriteLine("Обратный порядок перечисления");
 foreach(Person agent in agents.ReverseIterator())
     Console.WriteLine(agent.ToString());
 Console.WriteLine("Перечисление языков программирования");
         foreach (ProgrammingLanguages lang in
                           agents.LanguagesIterator())
     Console.WriteLine(lang.ToString());

Обратите внимание: когда клиент организует цикл foreach, он указывает не только имя объекта класса, но и имя итератора, организующего перечислимость. Результаты работы теста показаны на рис. 5.9.

Перечислимость и итераторы

Рис. 5.9. Перечислимость и итераторы

Интерфейс IEnumerator

Итераторы и контейнеры со встроенным методом GetEnumerator позволяют достаточно просто организовать перечислимость объектов класса, позволяя клиентам класса использовать цикл foreach для доступа к объектам на чтение. В основе предоставляемого механизма доступа лежат методы интерфейса IEnumerator. Используя итератор или встроенный метод GetEnumerator, можно не вникать в суть того, как, например, метод GetEnumerator создает объект интерфейса IEnumerator. Тем не менее в ряде случаев полезно при организации перечислимости объектов класса объявить класс наследником интерфейса IEnumerator и реализовать самостоятельно методы этого интерфейса.

Реализация в классе свойств и методов этого интерфейса позволяет клиентам класса самостоятельно организовать работу с объектами класса как с коллекцией. Обычно это не делается, а используются возможности, предоставляемые циклом for each, который скрывает детали применения свойств и методов интерфейса.

Общая схема перечисления объектов в коллекции такова. Коллекция рассматривается как список с курсором. Вводится понятие позиции курсора, указывающей на текущий объект из списка. В начальный момент курсор установлен перед первым объектом списка и текущий объект не определен. Курсор можно передвигать к следующему элементу списка. Можно прочесть значение элемента, на который указывает курсор. Методы интерфейса IEnumerator позволяют реализовать такую схему работы с коллекцией объектов. Если методы интерфейса предполагается сделать доступными для клиентов, то они реализуются как открытые методы.

Метод bool MoveNext () передвигает курсор к следующему элементу списка, так что после его первого вызова курсор будет указывать на первый элемент списка. После его вызова, когда курсор указывает на последний элемент списка, текущий элемент снова становится неопределенным, а позиция курсора смещается за последний элемент. Метод MoveNext возвращает значение true, когда курсор указывает на элемент списка, и возвращает false, когда курсор вне списка элементов.

Метод void Reset() возвращает курсор в начальную позицию перед первым элементом списка.

Метод-свойство Object Current { get } возвращает текущий элемент списка. Выбрасывается исключение InvalidOperationException,если при вызове свойства Current курсор установлен перед или после списка элементов.

Приведу теперь пример класса, реализующего интерфейс IEnumerator. Давайте построим класс People, который будет наследником класса Persons и интерфейса IEnumerator. Будучи наследником класса Persons, новый класс будет обладать всеми возможностями родительского класса, в том числе перечислимостью объектов. Но как наследник интерфейса IEnumerator он реализует методы интерфейса и предоставит своим клиентам новые возможности.

/// <summary>
    /// Наследник класса Persons
    /// Реализует методы интерфейса IEnumerator
    /// Предоставляет клиентам класса возможность
    /// собственной организации перебора объектов
    /// </summary>
    class People:Persons,IEnumerator
    {        
        int position = -1;
        public People() : base() { }
        public People(int size): base(size) { }
        public People(Person[] people) : base(people) { }

        /// <summary>
        /// Передвигает курсор к следующему элементу в контейнере
        /// Возвращает true, если курсор установлен на элементе списка,
        /// false - в противном случае
        /// </summary>
        /// <returns></returns>
        public bool MoveNext()
        {
            if (position < size) position++;
            return (position < size);
        }
        /// <summary>
        /// Восстанавливает текущее положение курсора 
        /// в контейнере объектов 
        /// </summary>
        public void Reset()
        {
            position = -1;
        }
        /// <summary>
        /// Возвращает текущий объект, если курсор установлен 
        /// на элементе списка
        /// Выбрасывает исключение InvalidOperationException,
        /// когда курсор вне списка
        /// </summary>        
        public object Current
        {
            get
            {
                try
                {
                    return container[position];
                }

                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
            set
            {
                try
                {
                    container[position] = (Person)value;
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

У класса три собственных конструктора, которые сводятся к вызову конструкторов родителя, поле position, необходимое для организации доступа, и три открытых метода, реализующих методы интерфейса IEnumerator. Методы устроены достаточно просто и в особых комментариях не нуждаются. Но стоит обратить внимание на реализацию метода-свойства Current. В самом интерфейсе для этого свойства указан только метод get. В реализации я добавил и реализацию метода set. Это сразу же расширяет возможности клиента. Если при работе с методом foreach объекты доступны только для чтения, то теперь свойство Current позволяет изменять элементы коллекции. Тест, построенный для работы с новым классом People, демонстрирует все эти возможности.

public void TestPeople()
 {     
     People people = new People();     
     foreach (Person person in people)
         Console.WriteLine(person.ToString());
     
     Console.WriteLine("Работа клиента с коллекцией!");     
     try
     {
         Person person;
         person = new Person("James Bond", 44);
         people.MoveNext();
         people.Current = person;
         people.Reset();
         while (true)
                {
             people.MoveNext();
             person = (Person)people.Current;
             Console.WriteLine(person.ToString());
         }
     }
     catch (InvalidOperationException)
     {
         people.Reset();
     }
 }

Результаты работы этого теста показаны на рис. 5.10.

Реализация интерфейса IEnumerator

Рис. 5.10. Реализация интерфейса IEnumerator

Обратите внимание: клиент сумел изменить объект, полученный в результате перечисления, добавив "Джеймса Бонда" в список агентов. На этой оптимистической ноте закончим тему организации перечислимости в классе.

Задачи

  1. Постройте DLL c классом Student, наследующим интерфейс IComparable. Постройте Windows-проект для работы с классом Student.
  2. Постройте DLL c классом Student, наследующим интерфейс IEnumerable (например, для перечисления оценок студента). Постройте Windows-проект для работы с классом Student.
  3. Постройте DLL c классом Student, наследующим интерфейсы IComparable и IEnumerable. Постройте Windows-проект для работы с классом Student.
  4. Постройте DLL c классами Student и Students. В классе Students задайте несколько итераторов, позволяющих перечислять студентов по разным критериям - фамилиям, рейтингу. Постройте Windows-проект для работы с классами.
  5. Постройте DLL c классами Student и Teacher, связанными взаимными отношениями клиент - поставщик. Обеспечьте возможность сериализации данных, используя атрибут сериализации и интерфейс ISerializable.
  6. Постройте DLL c классом Car, наследующим интерфейс IComparable. Постройте Windows- проект для работы с классом Car.
  7. Постройте DLL c классом Car, наследующим интерфейс IEnumerable (например, для перечисления характеристик машины). Постройте Windows-проект для работы с классом Car.
  8. Постройте DLL c классом Car, наследующим интерфейсы IComparable и IEnumerable. Постройте Windows-проект для работы с классом Car.
  9. Постройте DLL c классами Car и SaleOfCars. В классе SaleOfCars задайте несколько итераторов, позволяющих перечислять машины по разным критериям - маркам, стоимости. Постройте Windows-проект для работы с классами.
  10. Постройте DLL c классами Car и OwnerOfCar, связанных отношением клиент - поставщик. Обеспечьте возможность сериализации данных, используя атрибут сериализации и интерфейс ISerializable.
  11. Постройте DLL c классом Employee, наследующим интерфейс IComparable. Постройте Windows-проект для работы с классом Employee.
  12. Постройте DLL c классом Employee, наследующим интерфейс IEnumerable (например, для перечисления послужного списка). Постройте Windows-проект для работы с классом Employee.
  13. Постройте DLL c классом Employee, наследующим интерфейсы IComparable и IEnumerable. Постройте Windows-проект для работы с классом Employee.
  14. Постройте DLL c классами Employee и Firm. В классе Firm задайте несколько итераторов, позволяющих перечислять служащих по разным критериям - фамилиям, должности. Постройте Windows-проект для работы с классами.
  15. Постройте DLL c классами Employee и Firm, связанными взаимными отношениями клиент - поставщик. Обеспечьте возможность сериализации данных, используя атрибут сериализации и интерфейс ISerializable.
  16. Постройте DLL c классами Врач, Космонавт, Альпинист. Постройте классы-потомки Врач_Космонавт, Врач_Альпинист, наследующие свойства родительских классов. Используйте механизм интерфейсов. Постройте Windows-проект для работы с классами.
  17. Постройте DLL c классами Учитель, Шофер, Пилот. Постройте классы-потомки Инструктор_Водитель, Инструктор_Летчиков, наследующие свойства родительских классов. Используйте механизм интерфейсов. Постройте=Windows проект для работы с классами.
< Лекция 5 || Лекция 6: 12345 || Лекция 7 >
Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дарья Федотова
Дарья Федотова
Александр Мигунов
Александр Мигунов
Россия
Владимир Васильев
Владимир Васильев
Россия