Po co używać słowa kluczowego yield, skoro mogę użyć zwykłego IEnumerable?

Podany kod:

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

Dlaczego nie miałbym tego tak zakodować?:

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

W pewnym sensie rozumiem, co robi słowo kluczowe yield. Mówi kompilatorowi, aby zbudował pewien rodzaj rzeczy (iterator). Ale po co go używać? Poza tym, że jest to nieco mniej kodu, co to dla mnie robi?

 165
Author: Marcel Gosselin, 2012-12-27

8 answers

Użycie yield sprawia, że zbiór jest leniwy.

Powiedzmy, że potrzebujesz tylko pierwszych pięciu przedmiotów. Na twój sposób, muszę przejrzeć całą listę , Aby uzyskać pierwsze pięć pozycji. Z yield, ja tylko pętli przez pierwszych pięciu elementów.
 232
Author: Robert Harvey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-27 16:28:09

Zaletą bloków iteratora jest to, że działają leniwie. Możesz więc napisać metodę filtrowania w ten sposób:

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

To pozwoli Ci filtrować strumień tak długo, jak chcesz, nigdy nie buforując więcej niż jednego elementu na raz. Jeśli potrzebujesz tylko pierwszej wartości ze zwracanej sekwencji, na przykład, dlaczego chcesz skopiować Wszystko do nowej listy?

Jako kolejny przykład, możesz łatwo utworzyć nieskończony strumień za pomocą bloków iteratora. Na przykład, oto ciąg liczb losowych:

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

Jak zapisać nieskończoną sekwencję na liście?

My Edulinq blog series podaje przykładową implementację LINQ to Objects, która sprawia, że używa bloków iteratora. LINQ jest zasadniczo leniwy tam, gdzie może być - a umieszczanie rzeczy na liście po prostu nie działa w ten sposób.

 126
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-05 19:33:59

Z kodem "lista", musisz przetworzyć pełną listę, zanim będziesz mógł przekazać ją do następnego kroku. Wersja "yield" przekazuje przetworzony element natychmiast do następnego kroku. Jeśli ten "następny krok "zawiera".Take(10) "wtedy wersja "yield"przetworzy tylko pierwsze 10 elementów i zapomni o pozostałych. Kod "listy" przetworzyłby wszystko.

Oznacza to, że widzisz największą różnicę, gdy musisz wykonać dużo przetwarzania i / lub mieć długie listy elementów, aby proces.

 42
Author: Hans Kesting,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-27 16:29:19

Możesz użyć yield, aby zwrócić elementy, których nie ma na liście. Oto mała próbka, która może być nieskończenie powtarzana przez Listę, dopóki nie zostanie anulowana.

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);
    }
}

To pisze

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

...itd. do konsoli aż do odwołania.

 22
Author: Jason Whitted,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-03 06:50:54
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

Gdy powyższy kod jest używany do pętli przez filteredlist () i zakładając item.Name = = "James" będzie spełniony na 2 pozycji na liście, metoda wykorzystująca yield Da dwa razy. To leniwe zachowanie.

Gdzie jako metoda wykorzystująca list doda wszystkie N obiektów do listy i przekaże całą listę do wywołującej metody.

Jest to dokładnie przypadek użycia, w którym można wyróżnić różnicę między IEnumerable i IList.

 10
Author: humblelistener,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-03 09:16:34

Najlepszym przykładem użycia yield w świecie rzeczywistym byłoby obliczenie ciągu Fibonacciego.

Rozważ następujący kod:

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

To zwróci:

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

Jest to miłe, ponieważ pozwala szybko i łatwo obliczyć nieskończoną serię, dając możliwość korzystania z rozszerzeń Linq i zapytań tylko to, czego potrzebujesz.

 7
Author: Middas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-22 22:42:17

Dlaczego używać [yield]? Poza tym, że jest to nieco mniej kodu, co to dla mnie robi?

Czasami jest to przydatne, czasami nie. Jeśli cały zestaw danych musi zostać zbadany i zwrócony, to nie będzie żadnej korzyści w użyciu yield, ponieważ wszystko, co zrobił, to wprowadzenie kosztów ogólnych.

Kiedy wydajność naprawdę świeci, jest wtedy, gdy zwracany jest tylko częściowy zestaw. Myślę, że najlepszym przykładem jest sortowanie. Załóżmy, że masz listę obiektów zawierających datę i kwotę dolara z w tym roku i chcielibyście zobaczyć pierwszą garść (5) rekordów roku.

Aby to osiągnąć, lista musi być posortowana rosnąco według daty, a następnie należy pobrać pierwsze 5. Gdyby to było zrobione bez plonu, cała lista musiałaby być posortowana, aż do upewnienia się, że dwie ostatnie daty były w porządku.

Jednak z wydajnością, po ustaleniu pierwszych 5 pozycji sortowanie zatrzymuje się i wyniki są dostępne. Może to zaoszczędzić dużą ilość czas.

 1
Author: Travis J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-23 23:31:38

Instrukcja yield return pozwala zwracać tylko jeden element na raz. Zbierasz wszystkie przedmioty na liście i ponownie zwracasz tę listę, która jest nadmiarową pamięcią.

 0
Author: Prabhavith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-03 06:49:15