Интересное использование ключевого слова С# 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: Каждый раз. Конечно, вы можете кэшировать результат, превратив его в список, например, и повторить его после.