Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 111 112 113 114 115 116 117 118 119 ... 407
Reset(), существует более легкий путь. Поскольку тип System.Array (а также многие другие классы коллекций) уже реализует интерфейсы IEnumerable и IEnumerator, вы можете просто делегировать запрос к System.Array следующим образом (обратите внимание, что в файл кода понадобится импортировать пространство имен System.Collections):

using System.Collections;

...

public class Garage : IEnumerable

{

  // System.Array уже реализует IEnumerator!

  private Car[] carArray = new Car[4];

  public Garage()

  {

    carArray[0] = new Car("FeeFee", 200);

    carArray[1] = new Car("Clunker", 90);

    carArray[2] = new Car("Zippy", 30);

    carArray[3] = new Car("Fred", 30);

  }

 // Возвратить IEnumerator объекта массива.

  public IEnumerator GetEnumerator()

    => carArray.GetEnumerator();

}

После такого изменения тип Garage можно безопасно использовать внутри конструкции foreach. Более того, учитывая, что метод GetEnumerator() был определен как открытый, пользователь объекта может также взаимодействовать с типом IEnumerator:

// Вручную работать с IEnumerator.

IEnumerator carEnumerator = carLot.GetEnumerator();

carEnumerator.MoveNext();

Car myCar = (Car)i.Current;

Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);

Тем не менее, если вы предпочитаете скрыть функциональность IEnumerable на уровне объектов, то просто задействуйте явную реализацию интерфейса:

// Возвратить IEnumerator объекта массива.

IEnumerator IEnumerable.GetEnumerator()

  => return carArray.GetEnumerator();

В результате обычный пользователь объекта не обнаружит метод GetEnumerator() в классе Garage, в то время как конструкция foreach при необходимости будет получать интерфейс в фоновом режиме.

Построение итераторных методов с использованием ключевого слова yield

Существует альтернативный способ построения типов, которые работают с циклом foreach, предусматривающий использование итераторов. Попросту говоря, итератор — это член, который указывает, каким образом должны возвращаться внутренние элементы контейнера во время обработки в цикле foreach. В целях иллюстрации создайте новый проект консольного приложения по имени CustomEnumeratorWithYield и вставьте в него типы Car, Radio и Garage из предыдущего примера (снова переименовав пространство имен согласно текущему проекту). Затем модифицируйте тип Garage:

public class Garage : IEnumerable

{

  ...

  // Итераторный метод.

  public IEnumerator GetEnumerator()

  {

    foreach (Car c in carArray)

    {

      yield return c;

    }

  }

}

Обратите внимание, что показанная реализация метода GetEnumerator() осуществляет проход по элементам с применением внутренней логики foreach и возвращает каждый объект Car вызывающему коду, используя синтаксис yield return. Ключевое слово yield применяется для указания значения или значений, которые подлежат возвращению конструкцией foreach вызывающему коду. При достижении оператора yield return текущее местоположение в контейнере сохраняется и выполнение возобновляется с этого местоположения, когда итератор вызывается в следующий раз.

Итераторные методы не обязаны использовать ключевое слово foreach для возвращения своего содержимого. Итераторный метод допускается определять и так:

public IEnumerator GetEnumerator()

{

   yield return carArray[0];

   yield return carArray[1];

   yield return carArray[2];

   yield return carArray[3];

}

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

Защитные конструкции с использованием локальных функций (нововведение в версии 7.0)

До первого прохода по элементам (или доступа к любому элементу) никакой код в методе GetEnumerator() не выполняется. Таким образом, если до выполнения оператора yield возникает условие для исключения, то оно не будет сгенерировано при первом вызове метода, а лишь во время первого вызова MoveNext().

Чтобы проверить это, модифицируйте GetEnumerator():

public IEnumerator GetEnumerator()

{

  // Исключение не сгенерируется до тех пор, пока не будет вызван

  // метод MoveNext().

  throw new Exception("This won't get called");

  foreach (Car c in carArray)

  {

    yield return c;

  }

}

Если функция вызывается, как показано далее, и больше ничего не делается, тогда исключение никогда не сгенерируется:

using System.Collections;

...

Console.WriteLine("***** Fun with the Yield Keyword *****n");

Garage carLot = new Garage();

IEnumerator carEnumerator = carLot.GetEnumerator();

Console.ReadLine();

Код выполнится только после вызова MoveNext() и сгенерируется исключение. В зависимости от нужд программы это может быть как вполне нормально, так и нет. Ваш метод GetEnumerator() может иметь защитную конструкцию, которую необходимо выполнить при вызове метода в первый раз. В качестве примера предположим, что список формируется из базы данных. Вам может понадобиться организовать проверку, открыто ли подключение к базе данных, во время вызова метода, а не при проходе по списку. Или же может возникнуть потребность в проверке достоверности входных параметров метода Iterator(), который рассматривается далее.

Вспомните средство локальных функций версии C# 7, представленное в главе 4; локальные функции — это закрытые функции, которые определены внутри других функций. За счет перемещения yield return внутрь локальной функции, которая возвращается из главного тела метода, операторы верхнего уровня (до возвращения локальной функции) выполняются немедленно. Локальная функция выполняется при вызове MoveNext().

Приведите метод к следующему виду:

public IEnumerator GetEnumerator()

{

  // Это исключение сгенерируется немедленно

  throw new Exception("This will get called");

  return ActualImplementation();

  // Локальная функция и фактическая реализация IEnumerator

  IEnumerator ActualImplementation()

  {

    foreach (Car c in carArray)

    {

      yield return c;

    }

  }

}

Ниже показан тестовый код:

Console.WriteLine("***** Fun with the Yield Keyword *****n");

Garage carLot = new Garage();

try

{

  //

1 ... 111 112 113 114 115 116 117 118 119 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

Оставить комментарий