Właściwe stosowanie "zwrotu z plonu"

Słowo kluczowe yield jest jednym z tych słów kluczowych W C#, które nadal mnie zadziwiają i nigdy nie byłem pewien, czy używam go poprawnie.

Z następujących dwóch fragmentów kodu, który jest preferowany i dlaczego?

Wersja 1: Using yield return

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}

Wersja 2: zwróć listę

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}
Author: Sachin Joseph, 2009-01-03

14 answers

Zwykle używam yield-return, gdy obliczam następny element na liście (lub nawet następną grupę elementów).

Używając wersji 2, musisz mieć pełną listę przed powrotem. Używając yield-return, tak naprawdę musisz mieć tylko następny element przed powrotem.

Między innymi pomaga to rozłożyć koszt obliczeniowy złożonych obliczeń na większe ramy czasowe. Na przykład, jeśli lista jest podłączona do GUI, a użytkownik nigdy nie przejdzie do ostatniej strony, nigdy nie obliczaj końcowych pozycji na liście.

Innym przypadkiem, w którym wydajność-zwrot jest korzystniejszy, jest sytuacja, gdy IEnumerable reprezentuje zbiór nieskończony. Rozważmy listę liczb pierwszych lub nieskończoną listę liczb losowych. Nigdy nie możesz zwrócić pełnej liczby na raz, więc użyj yield-return, aby zwrócić listę stopniowo.

W twoim konkretnym przykładzie masz pełną listę produktów, więc użyłbym wersji 2.

 821
Author: abelenky,
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-01-03 23:01:41

Wypełnianie tymczasowej listy jest jak pobieranie całego filmu, podczas gdy korzystanie z {[0] } jest jak strumieniowe przesyłanie tego filmu.

 659
Author: anar khalilov,
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
2016-06-02 15:09:54

Jako koncepcyjny przykład do zrozumienia, kiedy powinieneś użyć yield, powiedzmy, że metoda ConsumeLoop() przetwarza elementy zwrócone / uzyskane przez ProduceList():

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

Bez yield, wywołanie ProduceList() może zająć dużo czasu, ponieważ musisz wypełnić listę przed powrotem:

//pseudo-assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]

Używając yield, staje się przestawiony, jakby przeplatany:

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately yield & Consume
Produce consumable[1]                   // ConsumeLoop iterates, requesting next item
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

I na koniec, jak wielu wcześniej sugerowało, powinieneś użyć wersji 2, ponieważ masz już wypełnioną listę w każdym razie.

 75
Author: Kache,
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
2020-06-29 05:22:31

Wiem, że to stare pytanie, ale chciałbym podać jeden przykład kreatywnego wykorzystania słowa kluczowego yield. Mam naprawdę skorzystał z tej techniki. Mam nadzieję, że będzie to pomocne dla każdego, kto natknie się na to pytanie.

Uwaga: nie myśl o słowie kluczowym yield jako o innym sposobie budowania kolekcji. Duża część mocy plonu polega na tym, że egzekucja jest wstrzymana w Twoim metoda lub właściwość aż do wywołania kod jest powtarzany nad następną wartością. Oto mój przykład:

Używając słowa kluczowego yield (obok Roba Eisenburga Caliburn.Implementacja Micro coroutines ([12]}) pozwala mi na wyrażenie asynchronicznego wywołania do usługi sieciowej takiej jak:

public IEnumerable<IResult> HandleButtonClick() {
    yield return Show.Busy();

    var loginCall = new LoginResult(wsClient, Username, Password);
    yield return loginCall;
    this.IsLoggedIn = loginCall.Success;

    yield return Show.NotBusy();
}

Spowoduje to włączenie My BusyIndicator, wywołanie metody logowania w mojej usłudze internetowej, ustawienie flagi My IsLoggedIn na wartość zwracaną, a następnie wyłączenie BusyIndicator.

Oto Jak to działa: IResult ma metodę Execute i zakończone wydarzenie. Caliburn.Micro chwyta IEnumerator z wywołania do HandleButtonClick () i przekazuje go do Coroutine.Metoda BeginExecute. Metoda BeginExecute rozpoczyna iterację poprzez IResults. Gdy zwracany jest pierwszy IResult, wykonanie jest wstrzymywane wewnątrz funkcji HandleButtonClick (), a BeginExecute () dołącza obsługę zdarzenia do zakończonego zdarzenia i wywołuje Execute (). Jestem w drodze.Execute () może wykonać zadanie synchroniczne lub asynchroniczne i wywołać ukończone zdarzenie, gdy zrobione.

LoginResult wygląda mniej więcej tak:

public LoginResult : IResult {
    // Constructor to set private members...

    public void Execute(ActionExecutionContext context) {
        wsClient.LoginCompleted += (sender, e) => {
            this.Success = e.Result;
            Completed(this, new ResultCompletionEventArgs());
        };
        wsClient.Login(username, password);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    public bool Success { get; private set; }
}
[2]}może pomóc skonfigurować coś takiego i przejść przez egzekucję, aby obserwować, co się dzieje. Mam nadzieję, że to komuś pomoże! Bardzo mi się podobało odkrywanie różnych sposobów wykorzystania plonu.
 32
Author: Adam W. McKinley,
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
2011-05-18 04:36:41

Yield return może być bardzo wydajny dla algorytmów, w których trzeba iterację przez miliony obiektów. Rozważ poniższy przykład, w którym musisz obliczyć możliwe przejazdy do udostępniania jazdy. Najpierw generujemy możliwe wycieczki:

    static IEnumerable<Trip> CreatePossibleTrips()
    {
        for (int i = 0; i < 1000000; i++)
        {
            yield return new Trip
            {
                Id = i.ToString(),
                Driver = new Driver { Id = i.ToString() }
            };
        }
    }

Następnie powtarzaj przez każdą podróż:

    static void Main(string[] args)
    {
        foreach (var trip in CreatePossibleTrips())
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

Jeśli używasz List zamiast yield, będziesz musiał przydzielić 1 milion obiektów do pamięci (~190mb), a ten prosty przykład zajmie ~1400ms do uruchomienia. Jeśli jednak używasz yield, nie potrzebujesz aby umieścić wszystkie te obiekty temp w pamięci, a otrzymasz znacznie szybszą prędkość algorytmu: ten przykład zajmie tylko ~400ms, aby uruchomić bez zużycia pamięci w ogóle.

 19
Author: Andzej Maciusovic,
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
2020-01-06 10:37:37

Dwa kawałki kodu naprawdę robią dwie różne rzeczy. Pierwsza wersja będzie pobierać członków tak, jak ich potrzebujesz. Druga wersja załaduje wszystkie wyniki do pamięci zanim{[2] } zaczniesz cokolwiek z nią robić.

Nie ma na to dobrej ani złej odpowiedzi. Który z nich jest lepszy, zależy tylko od sytuacji. Na przykład, jeśli istnieje limit czasu, który musisz wypełnić zapytanie i musisz zrobić coś na wpół skomplikowanego z wynikami, drugi wersja może być preferowana. Ale uważaj na duże wyniki, zwłaszcza jeśli używasz tego kodu w trybie 32-bitowym. Byłem ugryziony przez OutOfMemory wyjątki kilka razy podczas wykonywania tej metody.

Kluczową rzeczą, o której należy pamiętać, jest to: różnice są w wydajności. Dlatego prawdopodobnie powinieneś wybrać ten, który ułatwi Twój kod i zmieni go dopiero po profilowaniu.

 13
Author: Jason Baker,
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-01-03 23:30:21

Wydajność ma dwa wielkie zastosowania

Pomaga zapewnić niestandardową iterację bez tworzenia tymczasowych kolekcji. ( ładowanie wszystkich danych i zapętlanie)

To pomaga zrobić stateful iteracji. (streaming)

Poniżej jest prosty film, który stworzyłem z pełną demonstracją w celu wsparcia powyższych dwóch punktów

Http://www.youtube.com/watch?v=4fju3xcm21M

 11
Author: Shivprasad Koirala,
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-04-05 18:48:34

To właśnie Chris Sells opowiada o tych wypowiedziach w języku programowania C# ;

Czasami zapominam, że zwrot plonu nie jest tym samym co zwrot, w że kod po zwróceniu yield może być wykonany. Na przykład kod po pierwszym zwróceniu tutaj nigdy nie może zostać wykonany:
int F() {
    return 1;
    return 2; // Can never be executed
}

Natomiast kod po pierwszym zwrocie plonu tutaj może być wykonanie:

IEnumerable<int> F() {
    yield return 1;
    yield return 2; // Can be executed
}

To często gryzie mnie w stwierdzeniu if:

IEnumerable<int> F() {
    if(...) {
        yield return 1; // I mean this to be the only thing returned
    }
    yield return 2; // Oops!
}

W tych przypadków, pamiętając, że zwrot plonów nie jest "ostateczny" jak zwrot jest pomocny.

 11
Author: Teoman shipahi,
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
2021-02-05 15:21:40

Zakładając, że twoja klasa LINQ używa podobnej wydajności do wyliczania / iteracji, pierwsza wersja jest bardziej wydajna, ponieważ daje tylko jedną wartość za każdym razem, gdy jest iterowana.

Drugi przykład to konwersja enumeratora/iteratora na Listę za pomocą metody ToList (). Oznacza to, że ręcznie iteruje wszystkie pozycje w wyliczeniu, a następnie zwraca płaską listę.

 8
Author: Soviut,
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-01-03 22:58:29

To jest trochę poza sednem, ale ponieważ pytanie jest oznaczone najlepszymi praktykami, pójdę dalej i dorzucę moje dwa grosze. Dla tego typu rzeczy bardzo wolę zrobić z niego nieruchomość:

public static IEnumerable<Product> AllProducts
{
    get {
        using (AdventureWorksEntities db = new AdventureWorksEntities()) {
            var products = from product in db.Product
                           select product;

            return products;
        }
    }
}

Jasne, jest to trochę więcej płyty kotłowej, ale kod, który tego używa, będzie wyglądał znacznie czystiej: {]}

prices = Whatever.AllProducts.Select (product => product.price);

Vs

prices = Whatever.GetAllProducts().Select (product => product.price);

Uwaga: nie robiłbym tego dla metod, które mogą zająć trochę czasu, aby wykonać swoją pracę.

 8
Author: Mark A. Nicolosi,
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-01-03 23:30:25

A co z tym?

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList();
    }
}
To chyba dużo czystsze. Nie mam jednak VS2008 pod ręką, aby to sprawdzić. W każdym razie, jeśli produkty implementują liczbę (jak się wydaje - jest to używane w foreach), zwróciłbym go bezpośrednio.
 6
Author: petr k.,
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-06-29 06:14:27

Użyłbym w tym przypadku wersji 2 kodu. Ponieważ masz pełną listę dostępnych produktów i to jest to, czego oczekuje "konsument" tego połączenia metody, byłoby wymagane, aby wysłać pełną informację z powrotem do rozmówcy.

Jeśli wywołanie tej metody wymaga" jednej " informacji na raz, a konsumpcja następnej informacji jest na żądanie, wtedy korzystne byłoby użycie yield return, który upewni się, że polecenie wykonania zostanie zwrócone do rozmówcy, gdy dostępna jest jednostka informacji.

Kilka przykładów, w których można użyć zwrotu wydajności to:

  1. złożone obliczenia krok po kroku, w których wywołujący oczekuje na dane kroku na raz
  2. stronicowanie w GUI-gdzie użytkownik może nigdy nie dotrzeć do ostatniej strony i tylko podzbiór informacji jest wymagany do ujawnienia na bieżącej stronie

Aby odpowiedzieć na twoje pytania, użyłbym wersji 2.

 5
Author: IntelligentBinary,
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
2016-09-28 14:04:54

Zwróć listę bezpośrednio. Zalety:

  • It ' s more clear
  • lista jest wielokrotnego użytku. (iterator nie jest) nie jest to prawda, dzięki Jon

Powinieneś używać iteratora (yield), gdy uważasz, że prawdopodobnie nie będziesz musiał iteracji aż do końca listy, lub gdy nie ma końca. Na przykład, wywołanie klienta będzie szukać pierwszego produktu, który spełnia pewne predykat, można rozważyć użycie iteratora, chociaż to wymyślony przykład i prawdopodobnie są lepsze sposoby, aby to osiągnąć. Zasadniczo, jeśli wiesz z góry, że cała lista będzie musiała zostać obliczona, po prostu zrób to z góry. Jeśli uważasz, że tak się nie stanie, rozważ użycie wersji iterator.

 3
Author: recursive,
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-01-03 23:25:01

Użycie yield jest podobne do słowa kluczowego return , z tym że zwrócigenerator. A obiekt generatora przemierzy tylko raz.

Wydajność ma dwie zalety:

  1. nie musisz czytać tych wartości dwa razy;
  2. możesz uzyskać wiele węzłów potomnych, ale nie musisz zapisywać ich wszystkich w pamięci.

Jest inne jasneWyjaśnienie może Ci pomóc.

 -4
Author: 123,
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-04-04 02:28:02