Jaki jest cel/zaleta używania iteratorów yield return W C#?

Wszystkie przykłady użycia yield return x; wewnątrz metody C# można zrobić w ten sam sposób, po prostu zwracając całą listę. W takich przypadkach, czy jest jakaś korzyść lub korzyść w korzystaniu ze składni yield return vs. zwracanie listy?

Ponadto, w jakich scenariuszach można by użyć yield return, że nie można po prostu zwrócić pełnej listy?

Author: CoderDennis, 2009-07-06

10 answers

A gdybyś sam budował kolekcję?

Ogólnie rzecz biorąc, Iteratory mogą być używane do generowania sekwencji obiektów. Na przykład metoda Enumerable.Range nie posiada wewnętrznie żadnej kolekcji. Po prostu generuje następną liczbę na żądanie . Jest wiele zastosowań tego leniwego generowania sekwencji przy użyciu maszyny stanowej. Większość z nich jest objęta koncepcjami programowania funkcyjnego .

Moim zdaniem, jeśli patrzysz na Iteratory tylko jako sposób na wyliczenie przez zbiór (to tylko jeden z najprostszych przypadków użycia), idziesz w złą stronę. Jak powiedziałem, Iteratory są środkami do zwracania sekwencji. Ciąg może być nawet nieskończony . Nie ma możliwości zwrócenia listy o nieskończonej długości i użycia pierwszych 100 pozycji. To ma być leniwym czasem. Zwracanie zbioru różni się znacznie od zwracania generatora zbioru (czym jest iterator). Porównuje jabłka do pomarańcze.

Hipotetyczny przykład:

static IEnumerable<int> GetPrimeNumbers() {
   for (int num = 2; ; ++num) 
       if (IsPrime(num))
           yield return num;
}

static void Main() { 
   foreach (var i in GetPrimeNumbers()) 
       if (i < 10000)
           Console.WriteLine(i);
       else
           break;
}

Ten przykład wyświetla liczby pierwsze mniejsze niż 10000. Możesz łatwo zmienić go na drukowanie liczb mniejszych niż milion bez dotykania algorytmu generowania liczb pierwszych. W tym przykładzie nie można zwrócić listy wszystkich liczb pierwszych, ponieważ sekwencja jest nieskończona, a konsument nawet nie wie, ile elementów chce od początku.

 109
Author: Mehrdad Afshari,
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
2009-07-06 21:35:16

Dobre odpowiedzi tutaj sugerują, że zaletą yield return jest to, że nie musisz tworzyć listy; listy mogą być drogie. (Również, po pewnym czasie, Znajdziesz je nieporęczne i nieeleganckie.)

A jeśli nie masz listy?

yield return pozwala na przemierzanie struktur danych (niekoniecznie list) na wiele sposobów. Na przykład, jeśli obiekt jest drzewem, możesz przemierzać węzły w zamówieniu przed lub po zamówieniu bez tworzenia innych list lub zmiany podstawowa struktura danych.

public IEnumerable<T> InOrder()
{
    foreach (T k in kids)
        foreach (T n in k.InOrder())
            yield return n;
    yield return (T) this;
}

public IEnumerable<T> PreOrder()
{
    yield return (T) this;
    foreach (T k in kids)
        foreach (T n in k.PreOrder())
            yield return n;
}
 24
Author: Ray,
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
2009-07-06 23:08:58

Leniwa Ocena / Odroczona Realizacja

Bloki iteratora "yield return" nie wykonają żadnego kodu, dopóki nie wywołasz tego konkretnego wyniku. Oznacza to, że mogą być również połączone ze sobą skutecznie. Pop quiz: zakładając, że funkcja "ReadLines ()" odczytuje wszystkie linie z pliku tekstowego i jest zaimplementowana przy użyciu bloku iteratora, ile razy poniższy kod będzie powtarzał się nad plikiem?

var query = ReadLines(@"C:\MyFile.txt")
                            .Where(l => l.Contains("search text") )
                            .Select(l => int.Parse(l.SubString(5,8))
                            .Where(i => i > 10 );

int sum=0;
foreach (int value in query) 
{
    sum += value;
}

Odpowiedź jest dokładnie jedna, i że nie aż sposób w pętli foreach.

Rozdzielenie obaw

Ponownie używając hipotetycznej funkcji ReadLines() z góry, możemy teraz łatwo oddzielić kod, który odczytuje plik od kodu, który odfiltrowuje niepotrzebne linie od kodu, który faktycznie przetwarza wyniki. Ten pierwszy, szczególnie, jest bardzo wielokrotnego użytku.

Nieskończone Listy

Zobacz moją odpowiedź na to pytanie na dobry przykład:
C# funkcja Fibonacciego zwracająca błędy

Zasadniczo, I zaimplementuj ciąg Fibonacciego używając bloku iteratora, który nigdy się nie zatrzyma (przynajmniej przed osiągnięciem MaxInt), a następnie użyj tej implementacji w bezpieczny sposób.

Ulepszona Semantyka

Jest to jedna z tych rzeczy, która jest o wiele trudniejsza do wyjaśnienia prozą, niż tylko tym, którzy z prostym wizualnym1:

Imperatyw a podział funkcjonalny obaw

Jeśli nie widzisz obrazu, pokazuje on dwie wersje tego samego kodu, z podświetleniami tła dla różnych problemów. Kod linq ma wszystkie kolory ładnie pogrupowane, podczas gdy tradycyjny kod imperatywny ma kolory mieszane. Autor argumentuje (i zgadzam się), że ten wynik jest typowy dla używania linq vs używania kodu imperatywnego... że linq lepiej porządkuje Twój kod tak, aby miał lepszy przepływ między sekcjami.


1 wierzę, że to jest oryginalne źródło: https://twitter.com/mariofusco/status/571999216039542784 . zauważ również, że ten kod to Java, ale C# będzie bądź podobny.

 15
Author: Joel Coehoorn,
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
2017-05-23 11:54:47

Czasami sekwencje, które musisz zwrócić są po prostu zbyt duże, aby zmieścić się w pamięci. Na przykład około 3 miesiące temu brałem udział w projekcie migracji danych pomiędzy bazami danych MS SLQ. Dane zostały wyeksportowane w formacie XML. Yield return okazał się całkiem przydatny z XmlReader . Ułatwiło to programowanie. Na przykład, załóżmy, że plik miał 1000 elementów Customer - Jeśli tylko wczytasz ten plik do pamięci, będzie to wymagało przechowywania wszystkich z nich w pamięci na w tym samym czasie, nawet jeśli są obsługiwane sekwencyjnie. Możesz więc używać iteratorów, aby przemierzać kolekcję jeden po drugim. W takim przypadku trzeba poświęcić tylko pamięć na jeden element.

Jak się okazało, użycie XmlReader dla naszego projektu było jedynym sposobem, aby aplikacja działała przez długi czas , ale przynajmniej nie zawiesiła całego systemu i nie wywołała OutOfMemoryException . Oczywiście możesz pracować z XmlReader bez wydajności Iteratory. Ale Iteratory znacznie ułatwiły mi życie (nie pisałbym kodu do importu tak szybko i bez problemów). Obejrzyj tę stronę, aby zobaczyć, jak Iteratory wydajności są używane do rozwiązywania rzeczywistych problemów (nie tylko naukowych z nieskończonymi sekwencjami).

 10
Author: SPIRiT_1984,
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
2010-03-24 14:25:31

W scenariuszach zabawek / demonstracji nie ma dużej różnicy. Ale są sytuacje, w których przydatne są Iteratory-czasami cała lista nie jest dostępna( np. strumienie) lub lista jest kosztowna obliczeniowo i mało prawdopodobne, aby była potrzebna w całości.

 9
Author: Dan Davies Brackett,
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
2009-07-06 18:13:23

Jeśli cała lista jest gigantyczna, może zżerać dużo pamięci po prostu siedzieć, podczas gdy z plonem grasz tylko tym, czego potrzebujesz, kiedy tego potrzebujesz, niezależnie od tego, ile przedmiotów jest.

 2
Author: nilamo,
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
2009-07-06 18:17:22

Spójrz na tę dyskusję na blogu Erica White ' a (doskonały blog przy okazji) na leniwy kontra chętny do oceny .

 2
Author: JP Alioto,
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
2009-07-06 18:17:25

Za pomocą yield return można iterację elementów bez konieczności tworzenia listy. Jeśli nie potrzebujesz listy, ale chcesz iterację nad jakimś zestawem elementów, łatwiej będzie napisać

foreach (var foo in GetSomeFoos()) {
    operate on foo
}

Niż

foreach (var foo in AllFoos) {
    if (some case where we do want to operate on foo) {
        operate on foo
    } else if (another case) {
        operate on foo
    }
}

Możesz umieścić całą logikę określającą, czy chcesz operować na foo wewnątrz metody, używając zwrotów wydajności, a pętla foreach może być znacznie bardziej zwięzła.

 2
Author: AgileJon,
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
2009-07-06 18:22:14

Oto moja poprzednia zaakceptowana odpowiedź na dokładnie to samo pytanie:

Yield keyword value added?

Innym sposobem patrzenia na metody iteratora jest to, że wykonują one ciężką pracę polegającą na odwróceniu algorytmu "na lewą stronę". Rozważ parser. Pobiera tekst ze strumienia, wyszukuje w nim wzorce i generuje logiczny opis treści na wysokim poziomie.

Teraz mogę to ułatwić sobie jako autor parsera, stosując podejście SAX, w którym mam callback interfejs, który powiadamiam, gdy tylko znajdę następny fragment wzoru. Tak więc w przypadku SAX, za każdym razem, gdy znajduję początek elementu, wywołuję metodę beginElement i tak dalej.

Ale to stwarza problemy dla moich użytkowników. Muszą zaimplementować interfejs obsługi, a więc muszą napisać klasę maszyny stanowej, która odpowiada na metody wywołania zwrotnego. Jest to trudne do zrobienia, więc najprostszą rzeczą do zrobienia jest użycie implementacji stock, która buduje drzewo DOM, a następnie będą mieli wygoda chodzenia po drzewie. Ale wtedy cała struktura zostaje buforowana w pamięci-niedobrze.

Ale może zamiast tego napiszę mój parser jako metodę iteracyjną?

IEnumerable<LanguageElement> Parse(Stream stream)
{
    // imperative code that pulls from the stream and occasionally 
    // does things like:

    yield return new BeginStatement("if");

    // and so on...
}

To nie będzie trudniejsze do napisania niż podejście callback-interface-wystarczy zwrócić obiekt pochodzący z mojej klasy bazowej LanguageElement zamiast wywoływać metodę callback.

Użytkownik może teraz używać foreach do pętli przez wyjście mojego parsera, więc otrzymuje bardzo wygodny imperatyw interfejs programowania.

Rezultatem jest to, że obie strony niestandardowego API wyglądają tak, jakby były pod kontrolą , a zatem są łatwiejsze do napisania i zrozumienia.

 2
Author: Daniel Earwicker,
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
2017-05-23 11:47:24

Podstawowym powodem użycia yield jest to, że generuje / zwraca listę samodzielnie. Możemy użyć zwróconej listy do dalszej iteracji.

 2
Author: Tapas Ranjan Singh,
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-11-23 10:03:29