2009-12-28 3 views
13

Работа с помощью обучающей программы (Professional ASP.NET MVC - Nerd ужин), я наткнулся на этот фрагмент кода:Интересное использование C# выход ключевого слова в 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).

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

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

  2. Почему стоит yield break;? Что это действительно делает здесь?

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

+3

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

ответ

17

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

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

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

+1 действительно понятный и полезный ответ, спасибо. Только один второстепенный вопрос, если вы не возражаете: можете ли вы придумать пример, где 'break break' * необходимо *? – devuxer

+1

Незначительное разъяснение - вы имеете в виду 'IEnumerator []' в нескольких местах, где вы говорите 'IEnumerable []'. –

+0

Таким образом, в примере необходим «выход break», потому что, если не создано «RuleViolation», оно выдаст исключение, не так ли? – Ronald

6

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; 
    }  
} 
+0

Уверен про "3" один раз "? Насколько я понимаю, это переоценивается каждый раз (см. Два других ответа здесь). –

+0

Я уточнил это. – ChaosPandion

+0

Если вы делаете список, почему бы не перебрать его? Путь 'yield' позволяет вам явно не создавать список. Например, в вашем случае: public IEnumerable Enumerate() { return return "item1"; return return "item2"; } –

2

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

1: Выход магия «Стоп и вернуться позже» ключевое слово, так что если заявления перед «активным» один были оценены.

2: разрыв выхода явно заканчивается перечисление (думает, "перерыв" в случае переключения)

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

Смежные вопросы