Интересное использование ключевого слова С# yield в уроке Nerd Dinner

Работа через учебник (профессиональный ASP.NET MVC - Nerd Dinner), я наткнулся на этот фрагмент кода:

public IEnumerable<RuleViolation> GetRuleViolations() {
    if (String.IsNullOrEmpty(Title))
        yield return new RuleViolation("Title required", "Title");
    if (String.IsNullOrEmpty(Description))
        yield return new RuleViolation("Description required","Description");
    if (String.IsNullOrEmpty(HostedBy))
        yield return new RuleViolation("HostedBy required", "HostedBy");
    if (String.IsNullOrEmpty(Address))
        yield return new RuleViolation("Address required", "Address");
    if (String.IsNullOrEmpty(Country))
        yield return new RuleViolation("Country required", "Country");
    if (String.IsNullOrEmpty(ContactPhone))
        yield return new RuleViolation("Phone# required", "ContactPhone");
    if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
        yield return new RuleViolation("Phone# does not match country", "ContactPhone");
    yield break;
}

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

Этот пример немного странный для меня. Я думаю, что это задержка создания каких-либо экземпляров RuleViolation, пока программист не запросит конкретный элемент в коллекции, используя либо for each, либо метод расширения LINQ, например .ElementAt(2).

Помимо этого, у меня есть несколько вопросов:

  • Когда выполняются условные части операторов if? Когда вызывается GetRuleViolations() или когда перечислимый фактически повторяется? Другими словами, если значение Title изменяется от null до Really Geeky Dinner между тем временем, которое я вызываю GetRuleViolations(), и временем, которое я пытаюсь на самом деле перебрать по нему, будет создано RuleViolation("Title required", "Title")?/p >

  • Почему требуется yield break;? Что это действительно делает здесь?

  • Скажем, Title является нулевым или пустым. Если я вызываю GetRuleViolations(), то перебираем результирующее перечислимое два раза подряд, сколько раз будет вызываться new RuleViolation("Title required", "Title")?

Ответ 1

Функция, содержащая команды yield, обрабатывается иначе, чем обычная функция. То, что происходит за кулисами при вызове этой функции, состоит в том, что анонимный тип построен из конкретного типа функции IEnumerable, функция создает объект этого типа и возвращает его. Анонимный класс содержит логику, которая выполняет тело функции до следующей команды yield для каждого вызова IEnumerable.MoveNext. Это немного вводит в заблуждение, тело функции не выполняется в одной партии, как обычная функция, а скорее в кусках, каждая часть выполняется, когда перечислитель перемещается на один шаг вперед.

Что касается ваших вопросов:

  • Как я уже сказал, каждый if запускается, когда вы перебираете следующий элемент.
  • yield break действительно не нужно в приведенном выше примере. То, что он делает, это завершение перечисления.
  • Каждый раз, когда вы перебираете перечислимый, вы снова принудительно выполняете выполнение кода. Поместите контрольную точку на соответствующую строку и проверьте себя.

Ответ 2

1) Возьмем этот более простой пример:

public void Enumerate()
{
    foreach (var item in EnumerateItems())
    {
        Console.WriteLine(item);
    }
}

public IEnumerable<string> EnumerateItems()
{
    yield return "item1";
    yield return "item2";
    yield break;
}

Каждый раз, когда вы вызываете MoveNext() из IEnumerator, код возвращается из точки yield и переходит к следующей исполняемой строке кода.

2) yield break; сообщит IEnumerator, что больше нечего перечислять.

3) один раз для перечисления.

Используя yield break;

public IEnumerable<string> EnumerateUntilEmpty()
{
    foreach (var name in nameList)
    {
        if (String.IsNullOrEmpty(name)) yield break;
        yield return name;
    }     
}

Ответ 3

Краткая версия:

1: выход - это ключевое слово "Остановить и вернуться позже", поэтому были учтены операторы if перед "активным".

2: break в результате явно заканчивает перечисление (подумайте "break" в случае коммутатора)

3: Каждый раз. Конечно, вы можете кэшировать результат, превратив его в список, например, и повторить его после.