using System.Collections;
public class Garage: IEnumerable{
// В System.Array уже есть реализация IEnumerator!
private Car[] carArray;
public Cars() {
carArray = new Car[4];
carArray[0] = new Car("FeeFee", 200, 0);
carArray[l] = new Car("Clunker", 90, 0);
carArray[2] = new Car("Zippy, 30, 0);
carArray[3] = new Car("Fjred", 30, 0);}
public IEnumerator GetEnumerator() {
// Возвращает IEnumerator объекта массива.
return carArray.GetEnumerator();
}
}
Теперь, после модификации типа Garage, вы можете использовать этот тип в конструкции foreach без опасений. К тому же, поскольку метод GetEnumerator() определен, как открытый, пользователь объекта тоже может взаимодействовать с типом IEnumerator.
// Manually work with IEnumerator.
IEnumerator I = carLot.GetEnumerator();
i.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine("{0} имеет скорость {1} км/ч", myCar.PetName, myCar.CurrSpeed);
Если вы предпочтете скрыть функциональные возможности IEnumerable на объектном уровне, то следует использовать явную реализацию интерфейса.
public IEnumerator IEnumerable.GetEnumerator() {
// Возвращает IEnumerator объекта массива.
return carArray.GetEnumerator();
}
Исходный код.Проект CustomEnumerator размещен в подкаталоге, соответствующем главе 7.
В .NET 1.x для того, чтобы пользовательские коллекции (такие, как Garage) допускали применение конструкции foreach в операциях, подобных перечислению, реализация интерфейса IEnumerable (и, как правило, интерфейса IEnumerator) была обязательной. В C# 2005 предлагается альтернативный вариант построения типов, позволяющих применение цикла foreach, – с помощью итераторов.
В упрощённой интерпретации итератор является членом, указывающим порядок возвращения внутренних элементов контейнера при их обработке с помощью foreach. И хотя метод итератора все равно должен называться GetEnumerator(), а возвращаемое значение все равно должно иметь тип IEnumerator, при таком подходе ваш пользовательский класс уже не обязан реализовывать все ожидаемые интерфейсы.
public class Garage { // Без реализации IEnumerator!
private Car[] carArray;
…
// Метод итератора.
public IEnumerator GetEnumerator() {
foreach (Car с in carArray) {
yield return c;
}
}
}
Обратите внимание на то, что данная реализация GetEnumerator() осуществляет "проход" по вложенным элементам, используя внутреннюю логику foreach, и возвращает объекты Car вызывающей стороне, используя новую синтаксическую конструкцию yield return. Ключевое слово yield используется для того, чтобы указать значение (или значения), возвращаемые конструкции foreach вызывающей стороны. Когда в программе встречается оператор yield return, сохраняется текущая позиция, и именно с этой позиции выполнение будет продолжено при следующем вызове итератора.
Когда компилятор C# обнаруживает метод итератора, в рамках области видимости соответствующего типа (в данном случае это Garage) динамически генерируется вложенный класс. Этот автоматически сгенерированный класс реализует интерфейсы IEnumerable и IEnumerator и указывает необходимые параметры членов GetEnumerator(), MoveNext(), Reset() и Current. Если теперь загрузить данное приложение в ildasm.exe, то будет видно, что внутренняя реализация GetEnumerator() в объекте Garage использует сгенерированный компилятором тип (который в данном примере получает имя ‹ GetEnumerator › d__0).
.method public hidebysig instance class [mscorlib] System.Collections.IEnumerator GetEnumerator() cil managed {
…
newobj instance void CustomEnumeratorWithYield.Garage/ '‹ GetEnumerator›d__0'::.ctor(int32)
…
} // end of method Garage::GetEnumerator
Явно, что от предложенного здесь определения метода итератора мы не получим большой пользы, поскольку наш тип Garage изначально реализовывал GetEnumerator(), ссылаясь на внутренний тип System.Array. Но синтаксис итератора C# может сэкономить немало времени при построении более "экзотических" пользовательских контейнеров (например, бинарных деревьев), где приходится вручную реализовать интерфейсы IEnumerator и IEnumerable. В любом случае программный код вызывающей стороны при взаимодействии с методом итератора с использованием foreach оказывается одинаковым.
static void Main(string[] args) {
Console.WriteLine("***** Забавы с методами итератора *****\n");
Garage carLot = new Garage();
foreach (Car с in carLot) {
Console.WriteLine("{0} имеет скорость {1} км/ч", с.PetName, с.CurrrSpeed);
}
Console.ReadLine();
}
Исходный код. Проект CustomEnumeratorWifhYield размещен в подкаталоге, соответствующем главе 7.
Читать дальше