2010-06-07 2 views

ответ

24

Если вы хотите deferred execution.

Это имеет смысл в большинстве случаев, когда альтернатива заключается в создании временной коллекции.

Рассмотрите этот сценарий: у меня есть список целых чисел, и я хочу перечислить их квадраты.

Я мог бы сделать это:

public static IEnumerable<int> Squares(this IEnumerable<int> numbers) { 
    List<int> squares = new List<int>(); 
    foreach (int number in numbers) 
     squares.Add(number * number); 

    return squares; 
} 

Тогда я мог бы просуммировать квадраты, взять их среднее значение, найти самый большой и т.д.

Но я действительно не нужно, чтобы заполнить весь новый List<int> для этой цели. Я мог бы использоваться yield перечислить через первоначальный список и вернуть квадраты один-на-один:

public static IEnumerable<int> Squares(this IEnumerable<int> numbers) { 
    foreach (int number in numbers) 
     yield return number * number; 
} 

Тот факт, что это на самом деле делает различие не может быть очевидным, пока вы не начнете дело с очень большими коллекциями, где заполнение временных коллекций оказывается весьма расточительным.

Например, предположим, что я хотел найти первый квадрат над определенным порогом. Я мог бы сделать это:

IEnumerable<int> numbers = GetLotsOfNumbers(); 
var squares = numbers.Squares(); 
int firstBigSquare = squares 
    .Where(x => x >= 1000) 
    .FirstOrDefault(); 

Но если мой Squares метод населен весь List<int> перед возвращением, приведенный выше код будет делать потенциально гораздо больше работы, чем это необходимо.

+2

Хорошо написанный пример. + 1, почему разница может быть не очевидной. – Paddy

10

От MSDN page on yield:

Используется в блоке итератора, чтобы обеспечить значение объекта нумератора или сигнал об окончании итерации.

Вы используете его при создании пользовательского итератора. Используя пример со страницы:

// yield-example.cs 
using System; 
using System.Collections; 
public class List 
{ 
    public static IEnumerable Power(int number, int exponent) 
    { 
     int counter = 0; 
     int result = 1; 
     while (counter++ < exponent) 
     { 
      result = result * number; 
      yield return result; 
     } 
    } 

    static void Main() 
    { 
     // Display powers of 2 up to the exponent 8: 
     foreach (int i in Power(2, 8)) 
     { 
      Console.Write("{0} ", i); 
     } 
    } 
} 

yield означает, что while петли внутри Power эффективно «паузу» после каждой итерации, чтобы процедура вызова выполнить какое-либо действие. В этом случае распечатайте результат.

8

Когда вы слишком ленивы, чтобы написать свой собственный IEnumerator;)

+3

+1 Я думаю, что я всегда слишком ленив ... –

+1

И, наверное, следует отметить, что существует разница между IEnumerator и IEnumerable, слова пишется аналогично достаточно, чтобы меня путали, потому что я читал разные определения для того, что я мысль был то же самое – Davy8

+0

@ Davy8 очень правда! Разница в том, что многие люди путаются. IEnumerator содержит всю логику, скрытую за урожаем; IEnumerable просто предоставит доступ к IEnumerator. –

1

См this article.

Выход действует как возвращаемый-placeholder - это нелокальная точка возврата goto, которая сохраняет среду метода и позволяет коду «прыгать» обратно.В некотором роде аналогичный (вид перевернутого) для передачи делегата в метод, который позволяет вам вводить определенную логику в рамках другого метода, закрытие позволяет вам выполнять различные типы работы «вокруг» более общего метода, позволяя вам сохранять код небольшим и модульные и повторно используемые.

Это может привести к значительно более эффективному коду. Вместо создания экземпляра очень большой коллекции можно было бы позволить отдельным объектам действовать последовательно (и они отбрасываются после каждой операции). Я предполагаю, что вы могли бы построить случаи, когда простой итератор было бы чрезвычайно сложно построить.

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