Слайд 1Интерфейс IEnumerable
Класс, наследующий интерфейс IEnumerable (англ. «Перечислимый»), может участвовать в
цикле foreach.
В случае с классом Person, использование индексатора позволило обращаться
к детям персоны по индексу. Однако попытка их перебора через цикл foreach завершится ошибкой:
foreach (Person ch in p.Children)
Console.WriteLine(ch.FirstName); // Ошибка доступа
Поскольку свойство Children представляет класс ArrayList, реализующий интерфейс IEnumerable, то для устранения ошибки достаточно сделать данное свойство открытым (public).
Однако есть другой способ устранения ошибки – реализовать в самом классе Person интерфейс IEnumerable, содержащий один метод – GetEnumerator(), возвращающий в качестве результата интерфейс IEnumerator (англ. «Счетчик»). Вариант реализации:
public IEnumerator GetEnumerator()
{
return Children.GetEnumerator();
}
Слайд 2Интерфейс IEnumerable
Поскольку для массива Children (класс ArrayList) метод GetEnumerator() интерфейса
IEnumerable уже реализован, мы воспользовались им для реализации данного метода
в классе Person. В результате цикл foreach в приведенном ранее примере начнет работать без ошибок.
Следует заметить, что в этом примере мы пошли на хитрость и воспользовались готовой реализацией метода GetEnumerator() из другого класса.
Поскольку результат метода GetEnumerator() имеет тип интерфейса IEnumerator, достаточно просто реализовать данный интерфейс и далее вернуть свой вариант реализации.
Интерфейс IEnumerator содержит три метода:
Current() – метод возвращает текущий элемент списка.
MoveNext() – метод осуществляет переход к следующему элементу списка (меняет значение индекса или ссылки, тем самым указывая на новый текущий элемент).
Reset() – метод производит сброс счетчика.
Слайд 3Интерфейс IEnumerable
Реализуем интерфейс IEnumerator в отдельном классе PersonEnumerator.
public class PersonEnumerator
: IEnumerator
{
int index = -1;
Person p;
public PersonEnumerator(Person p) // Конструктор
{
this.p
= p;
}
// Здесь будет реализация методов интерфейса IEnumerator
}
Слайд 4Интерфейс IEnumerable
Опишем в классе реализацию методов интерфейса IEnumerator:
public Object Current()
// Возврат текущего элемента
{
get { return p[index]; }
}
public bool MoveNext()
// Переход к следующему элементу
{
index++;
return ( index >= p.GetChildrenNumber() );
}
public void Reset() // Сброс счетчика
{
index = -1;
}
Слайд 5Интерфейс IEnumerable
Теперь следует изменить в классе Person реализацию метода GetEnumerator():
public
IEnumerator GetEnumerator()
{
return new PersonEnumerator(this);
}
В результате при вызове конструктор класса PersonEnumerator
получает в качестве параметра экземпляр класса Person, для которого становится возможным перебор детей (в том числе в цикле foreach).
Примечание. Возможны различные варианты реализации методов интерфейса IEnumerator. Например, метод MoveNext() может осуществлять обход списка детей в обратном порядке (в этом случае при инициализации и в методе Reset() индекс должен принимать начальное значение, равное количеству детей персоны).
Слайд 6Интерфейс IEnumerable
В C# существует способ реализовать метод GetEnumerator() без реализации
методов интерфейса IEnumerator. Для этого используется оператор yield. В этом
случае метод GetEnumerator() можно реализовать как:
public IEnumerator GetEnumerator()
{
foreach (Person ch in Children)
yield return ch;
}
Здесь в методе GetEnumerator() запускается ещё один цикл foreach, перебирающий все элементы списка Children. Конструкция yield return возвращает полученное значение внешнему итератору (первому циклу foreach) без прерывания работы метода GetEnumerator().
Замечание. Конструкция yield return не может использоваться внутри блока обработки исключительных ситуаций.
Слайд 7Интерфейсы IСomparer и IComparable
Интерфейсы IComparer и IComparable отвечают за сортировку
элементов массива.
Для элементов массивов, представляющих простые типы данных, платформа .NET
обладает информацией о том, как их сравнивать. Что касается элементов пользовательского типа (классов), то об описании логики их сравнения должен позаботиться сам пользователь-программист.
Попробуем описать в классе Person метод упорядочивания детей персоны:
public void SortChildren()
{
Children.Sort();
}
При создании класса Person мы не описывали правил, согласно которым платформа должна сортировать его объекты.
Слайд 8Интерфейсы IСomparer и IComparable
В результате мы получим ошибку при попытке
вызвать метод:
Person p = new Person("Иван", "Иванов");
p.AddChild("Игорь", "Иванов");
p.AddChild("Ярослав", "Иванов");
p.AddChild("Галина", "Иванов");
p.SortChildren();
// Ошибка сравнения двух элементов
Для того, чтобы устранить ошибку, следует реализовать в классе интерфейс IComparable, содержащий единственный метод CompareTo(). Данный метод сравнивает вызвавший его объект с объектом, переданным в метод в качестве параметра.
В результате метод возвращает число:
отрицательное, если текущий объект меньше переданного;
ноль, если объекты одинаковы;
положительное, если текущий объект больше переданного.
Слайд 9Интерфейсы IСomparer и IComparable
Вариант реализации метода CompareTo():
public int CompareTo(Object o)
{
Person
p = (Person) o;
string Name1 = ToString();
string Name2 = p.ToString();
return
Name1.CompareTo(Name2);
}
Метод ToString ранее был переопределен нами:
public new string ToString()
{
return FirstName + " " + SecondName;
}
Слайд 10Интерфейсы IСomparer и IComparable
В приведенном примере для организации объектов-персон мы
воспользовались возможностями класса string, в котором метод CompareTo() интерфейса IComparable
уже был реализован. В результате сортировка массива объектов класса Person была сведена к сортировке получаемых на их основе строк.
Если же требуется описать собственный алгоритм сортировки (например, упорядочить объекты по нескольким свойствам различного типа), следует реализовать интерфейс IComparer, у которого имеется единственный метод Compare(), получающий в качестве параметров два объекта, которые требуется сравнить. Возвращаемое методом значение аналогично возвращаемому методом CompareTo(). Возможный вариант реализации метода:
public int IComparer.Compare(Object p1, Object p2)
{
string Name1 = ((Person) p1).ToString();
string Name2 = ((Person) p2).ToString();
return Name1.CompareTo(Name2);
}
Слайд 11Интерфейсы IСomparer и IComparable
Для вызова метода Compare() следует вызвать метод
Sort(), в качестве параметра которого должен быть передан объект класса,
реализующего интерфейса IComparer. Например, для упорядочивания детей конкретной персоны можно передать в метод Sort() ссылку на этот же объект (для его класса Person интерфейс IComparer уже был нами реализован):
Children.Sort(this);
Однако в качестве параметра в метод может быть передан любой другой класс. Главное условие – этот класс должен реализовывать интерфейс IComparer и уметь сравнивать объекты класса, вызвавшего метод Sort().
Для иллюстрации данного принципа объявим класс SortTest, который реализует сортировку массива объектов класса Person в обратном порядке.
Слайд 12Интерфейсы IСomparer и IComparable
class SortTest : IComparer
{
int IComparer.Compare(Object p1, Object
p2)
{
string Name1 = ((Person) p1).ToString();
string Name2 = ((Person) p2).ToString();
return Name2.CompareTo(Name1);
}
}
В
отличие от класса Person, в классе SortTest метод CompareTo() вызывается объектом p2, а в качестве параметра метода используется объект p1. В результате массив объектов-персон сортируется в обратном порядке.
Слайд 13Интерфейсы IСomparer и IComparable
Объявленный класс SortTest можно использовать для сортировки
в обратном порядке массива детей персоны (которые тоже являются персонами).
Для этого массивом Children вызывается метод Sort(), параметром в который передается экземпляр класса SortTest, для которого нами был реализован метод Compare():
Children.Sort( new SortTest() );
Поскольку объект класса SortTest в дальнейшем нам не потребуется, мы обошлись без объявления ссылающейся на него переменной и создали объект-параметр сразу при вызове метода через ключевое слово new.