Навіщо використовувати ключове слово прибутковість, коли я міг просто використовувати звичайний IEnumerable?


171

Враховуючи цей код:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

Чому я не повинен просто кодувати це таким чином?

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

Я начебто розумію, що yieldробить ключове слово. Це вказує компілятору побудувати певний вид речі (ітератор). Але навіщо це використовувати? Окрім того, що це трохи менший код, що це робить для мене?


28
Я усвідомлюю, що це лише приклад, але насправді код повинен бути написаний так: FullList.Where(IsItemInPartialList):)
BlueRaja - Danny Pflughoeft

Відповіді:


241

Використання yieldробить колекцію лінивою.

Скажімо, вам потрібно лише перші п’ять предметів. По-твоєму, я повинен проглянути весь список, щоб отримати перші п’ять елементів. З yield, я переглядаю лише перші п’ять пунктів.


14
Зауважте, що використовувати FullList.Where(IsItemInPartialList)буде так само ліниво. Тільки для цього потрібен набагато менший створений компілятором спеціальний --- пістолет --- код. І менше часу для написання та обслуговування розробника. (Звичайно, це був лише такий приклад)
12.12

4
Це Лінк, чи не так? Я думаю, Linq робить щось дуже схоже під ковдрами.
Роберт Харві

1
Так, Linq використовував уповільнене виконання ( yield return) там, де це було можливо.
Чад Шуггінс

11
Не забувайте, що якщо виписка про повернення прибутковості ніколи не виконується, ви все одно отримаєте порожній результат колекції, тому не потрібно турбуватися про нульовий виняток посилання. повернення врожаю приголомшує шоколадні бризки.
Бритва

127

Користь блоків ітераторів полягає в тому, що вони працюють ліниво. Таким чином, ви можете написати такий спосіб фільтрації:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

Це дозволить вам фільтрувати потік скільки завгодно, ніколи не буферуючи більше, ніж один елемент одночасно. Якщо вам потрібне лише перше значення з повернутої послідовності, наприклад, чому б ви хотіли скопіювати все у новий список?

Як інший приклад, ви можете легко створити нескінченний потік за допомогою ітераторних блоків. Наприклад, ось послідовність випадкових чисел:

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

Як би ви зберігали нескінченну послідовність у списку?

Моя серія блог Edulinq дає приклад реалізацію LINQ до об'єктів , що робить важке використання блоків ітераторів. LINQ в основному ледачий, де він може бути - і внесення речей до списку просто не працює таким чином.


1
Я не впевнений, подобається вам RandomSequenceчи ні. Для мене, безліч значень - перше і головне - те, що я можу повторити передбачення, але це, очевидно, призведе до нескінченного циклу. Я вважаю це досить небезпечним зловживанням концепцією IEnumerable, але YMMV.
Себастьян Неграш

5
@SebastianNegraszus: Послідовність випадкових чисел логічно нескінченна. IEnumerable<BigInteger>Наприклад, ви можете легко створити представницьку послідовність Фібоначчі. Ви можете користуватися foreachним, але нічого про IEnumerable<T>гарантії, що воно буде кінцевим.
Джон Скіт

42

За допомогою коду "список" вам доведеться обробити повний список, перш ніж ви зможете передати його на наступний крок. Версія "вихід" передає оброблений елемент негайно на наступний крок. Якщо цей "наступний крок" містить ".Take (10)", тоді версія "вихід" обробить лише перші 10 елементів і забуде про решту. Код «списку» обробив би все.

Це означає, що ви бачите найбільшу різницю, коли вам потрібно зробити багато обробки та / або мати довгі списки елементів для обробки.


23

Ви можете використовувати yieldдля повернення елементів, яких немає в списку. Ось невеликий зразок, який може нескінченно повторюватись до списку до скасування.

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

Про це пише

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

... тощо. до консолі до скасування.


10
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

Коли вищевказаний код використовується для проходження циклу через FilteredList () та припущенні item.Name == "Джеймс" буде задоволений другим елементом у списку, метод, що використовує, yieldотримає двічі. Це лінива поведінка.

Де як метод, що використовує список, додасть до списку всі n об’єктів і передасть повний список методу виклику.

Це саме той випадок використання, коли можна виділити різницю між IEnumerable та IList.


7

Найкращим прикладом реального світу, який я бачив для використання, yieldбуло б обчислення послідовності Фібоначчі.

Розглянемо наступний код:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

Це поверне:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

Це приємно, оскільки дозволяє швидко та легко обчислити нескінченну серію, даючи можливість використовувати розширення Linq та запитувати лише те, що потрібно.


5
Я не бачу нічого "реального світу" в обчисленні послідовності Фібоначчі.
Нек

Я згоден, що це насправді не "реальний світ", а яка крута ідея.
Кейсі

1

навіщо використовувати [урожай]? Окрім того, що це трохи менший код, що це робить для мене?

Іноді це корисно, іноді ні. Якщо весь набір даних необхідно перевірити і повернути, то використання врожайності не буде ніякої користі, оскільки все, що він робив, було внести накладні витрати.

Коли врожайність дійсно світить - це повертається лише частковий набір. Я думаю, що найкращим прикладом є сортування. Припустимо, у вас є список об’єктів, що містять дату та суму в доларі з цього року, і ви хочете побачити перші жменьки (5) року.

Для цього список необхідно відсортувати за датою, а потім взяти перші 5. Якби це було зроблено без урожайності, весь список повинен був би бути відсортований, аж до того, щоб переконатися, що останні дві дати в порядку.

Однак із урожайністю, після встановлення перших 5 найменувань, сортування припиняється і результати є доступними. Це може заощадити велику кількість часу.


0

Заява про повернення доходу дозволяє одночасно повертати лише один елемент. Ви збираєте всі елементи зі списку і знову повертаєте цей список, який є накладними на пам'ять.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.