Co oznacza „spadek wydajności;” robić w C #?

494

Widziałem tę składnię w MSDN: yield breakale nie wiem, co ona robi. Czy ktoś wie?

skb
źródło
26
Jednak dokumentacja MSDN wspomina o tym, ale go nie wyjaśnia. To zły sposób na dokuczanie głodnemu wiedzy deweloperowi.
Phil
3
Zwrot wydajności eliminuje potrzebę tworzenia listy kopii zapasowych, co oznacza, że ​​nie trzeba kodować czegoś takiego jak MyList.Add(...)po prostu zrobić yield return .... Jeśli musisz przedwcześnie wyjść z pętli i zwrócić wirtualną listę kopii zapasowych, której używaszyield break;
The Muffin Man

Odpowiedzi:

529

Określa, że ​​iterator dobiegł końca. Można myśleć yield breakjako returnoświadczenia, które nie zwraca wartości.

Na przykład, jeśli zdefiniujesz funkcję jako iterator, jej treść może wyglądać następująco:

for (int i = 0; i < 5; i++)
{
    yield return i;
}

Console.Out.WriteLine("You will see me");

Zauważ, że po zakończeniu wszystkich cykli, ostatnia linia zostanie wykonana i zobaczysz komunikat w aplikacji na konsolę.

Lub tak z yield break:

int i = 0;
while (true)
{
    if (i < 5)
    {
        yield return i;
    }
    else
    {
        // note that i++ will not be executed after this
        yield break;
    }
    i++;
}

Console.Out.WriteLine("Won't see me");

W takim przypadku ostatnia instrukcja nigdy nie jest wykonywana, ponieważ wcześniej opuściliśmy funkcję.

Damir Zekić
źródło
8
Czy może to być po prostu breakzamiast yield breakw powyższym przykładzie? Kompilator na to nie narzeka.
orad
85
@ lub zwykły breakw tym przypadku zatrzymałby pętlę, ale nie przerwałby wykonywania metody, dlatego ostatni wiersz zostałby wykonany, a tekst „Nie zobaczyłby mnie” byłby rzeczywiście widoczny.
Damir Zekić
5
@Damir Zekić Czy mógłbyś również dodać do swojej odpowiedzi, dlaczego powinieneś preferować podział rentowności zamiast zwrotu i jakie są różnice między nimi?
Bruno Costa
11
@ DamirZekić zwraca wartość null, a podział zysków nie jest taki sam. Jeśli zwrócisz null, możesz NullReferenceExceptionuzyskać moduł wyliczający IEnumerable, podczas gdy przy podziale wydajności nie będziesz (istnieje instancja bez elementów).
Bruno Costa
6
@BrunoCosta Próba regularnych zwrotów w tej samej metodzie powoduje błąd kompilatora. Za pomocą yield return xalertów kompilatora chcesz, aby ta metoda była cukrem syntaktycznym do tworzenia Enumeratorobiektu. Ten moduł wyliczający ma metodę MoveNext()i właściwość Current. MoveNext () wykonuje metodę aż do yield returninstrukcji i zamienia tę wartość na Current. Przy następnym wywołaniu MoveNext wykonywanie będzie kontynuowane od tego momentu. yield breakustawia Current na null, sygnalizując koniec tego modułu wyliczającego, tak aby foreach (var x in myEnum())koniec się zakończył.
Wolfzoon,
57

Kończy blok iteratora (np. Mówi, że w IEnumerable nie ma więcej elementów).

Brian
źródło
2
z [+1] - chociaż naukowo nie ma iteratorów w .NET. tylko wyliczacze (w jednym kierunku, do przodu)
Shaun Wilson
@Shaun Wilson, ale za pomocą yieldsłowa kluczowego możesz iterować kolekcję w obu kierunkach, ponadto możesz wziąć każdy kolejny element kolekcji nie z rzędu
monstr
@monstr możesz iterować kolekcję w dowolny sposób i nie musisz yieldtego robić. nie mówię, że się mylisz, rozumiem, co sugerujesz; ale naukowo w .NET nie ma iteratorów, tylko enumeratory (jeden kierunek, do przodu) - w przeciwieństwie do stdc ++ nie ma „ogólnej struktury iteratora” zdefiniowanej w CTS / CLR. LINQ pomaga zlikwidować lukę za pomocą metod rozszerzenia, które wykorzystują, yield return a także metod wywołania zwrotnego , ale są to metody rozszerzenia pierwszej klasy, a nie iteratory pierwszej klasy. wynikowa IEnumerablesama w sobie nie może iterować w żadnym innym kierunku niż przekierowanie do dzwoniącego.
Shaun Wilson,
29

Mówi iteratorowi, że osiągnął koniec.

Jako przykład:

public interface INode
{
    IEnumerable<Node> GetChildren();
}

public class NodeWithTenChildren : INode
{
    private Node[] m_children = new Node[10];

    public IEnumerable<Node> GetChildren()
    {
        for( int n = 0; n < 10; ++n )
        {
            yield return m_children[ n ];
        }
    }
}

public class NodeWithNoChildren : INode
{
    public IEnumerable<Node> GetChildren()
    {
        yield break;
    }
}
Pułapka
źródło
21

yieldw zasadzie sprawia, że IEnumerable<T>metoda zachowuje się podobnie do zaplanowanego wątku kooperacyjnego (w przeciwieństwie do zapobiegawczego).

yield returnprzypomina wątek wywołujący funkcję „harmonogramu” lub „uśpienia”, aby zrezygnować z kontroli nad procesorem. Podobnie jak wątek, IEnumerable<T>metoda natychmiast odzyskuje kontrolę w punkcie natychmiastowym, przy czym wszystkie zmienne lokalne mają takie same wartości, jak przed rezygnacją z kontroli.

yield break jest jak nić sięgająca końca swojej funkcji i kończąca się.

Ludzie mówią o „maszynie stanu”, ale maszyna stanu to tak naprawdę „wątek”. Wątek ma pewien stan (tj. Wartości zmiennych lokalnych) i za każdym razem, gdy jest planowany, podejmuje pewne działania w celu osiągnięcia nowego stanu. Kluczową kwestią yieldjest to, że w przeciwieństwie do wątków systemu operacyjnego, do których jesteśmy przyzwyczajeni, kod, który go używa, jest zamrożony w czasie, aż iteracja zostanie ręcznie zaawansowana lub zakończona.


źródło
9

yield breakOświadczenie powoduje wyliczanie przestać. W efekcie yield breakwykonuje wyliczenie bez zwracania żadnych dodatkowych elementów.

Rozważ, że istnieją dwa sposoby, aby metoda iteracyjna mogła zatrzymać iterację. W jednym przypadku logika metody może naturalnie wyjść z metody po zwróceniu wszystkich elementów. Oto przykład:

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount)
{
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;
    }

    Debug.WriteLine("All the primes were found.");
}

W powyższym przykładzie metoda iteratora w naturalny sposób przestanie maxCountdziałać po znalezieniu liczb pierwszych.

yield breakStwierdzenie jest kolejnym sposobem na iterator do zaprzestania wyliczanie. Jest to sposób na wczesne wyjście z wyliczenia. Oto ta sama metoda, jak powyżej. Tym razem metoda ma limit czasu, który metoda może wykonać.

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount, int maxMinutes)
{
    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;

        if (sw.Elapsed.TotalMinutes > maxMinutes)
            yield break;
    }

    Debug.WriteLine("All the primes were found.");
}

Zwróć uwagę na połączenie z yield break. W efekcie wychodzi z wyliczenia wcześniej.

Zauważ też, że yield breakdziała inaczej niż zwykła break. W powyższym przykładzie yield breakkończy metodę bez wykonywania wywołania Debug.WriteLine(..).

Wallace Kelly
źródło
7

Tutaj http://www.alteridem.net/2007/08/22/the-yield-statement-in-c/ jest bardzo dobrym przykładem:

public static IEnumerable <int> Zakres (int min, int max)
{
   podczas gdy (prawda)
   {
      jeśli (min> = maks.)
      {
         spadek wydajności;
      }
      zwrot wydajności min ++;
   }
}

i wyjaśnienie, że jeśli yield breakinstrukcja zostanie trafiona w metodzie, wykonanie tej metody kończy się bez powrotu. Są sytuacje czasowe, w których nie chcesz dawać żadnych rezultatów, możesz użyć podziału zysków.

Towhid
źródło
5

podział zysków jest tylko sposobem na powiedzenie zwrotu po raz ostatni i nie zwraca żadnej wartości

na przykład

// returns 1,2,3,4,5
IEnumerable<int> CountToFive()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield break;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
 }
John ClearZ
źródło
4

Jeśli to, co rozumiesz przez „to, co naprawdę robi podział zysków”, to „jak to działa” - zobacz blog Raymonda Chena, aby uzyskać szczegółowe informacje https://devblogs.microsoft.com/oldnewthing/20080812-00/?p=21273

Iteratory C # generują bardzo skomplikowany kod.

Tony Lee
źródło
12
Cóż, są skomplikowane tylko wtedy, gdy zależy ci na kodzie, w który zostały wkompilowane. Kod, który ich używa, jest dość prosty.
Robert Rossney
@Robert, właśnie to miałem na myśli, dlatego zaktualizowałem odpowiedź, dodając twój komentarz
Tony Lee
-2

Słowo kluczowe fed jest używane razem ze słowem kluczowym return, aby zapewnić wartość dla obiektu wyliczającego. Zwrot wydajności określa zwracaną wartość lub wartości. Po osiągnięciu instrukcji zwrotu z zysku bieżąca lokalizacja jest zapisywana. Wykonanie jest ponownie uruchamiane z tej lokalizacji przy następnym wywołaniu iteratora.

Aby wyjaśnić znaczenie na przykładzie:

    public IEnumerable<int> SampleNumbers()
    {
        int counter = 0;
        yield return counter;

        counter = counter + 2;

        yield return counter;

        counter = counter + 3;

        yield return counter ;
    }

Wartości zwracane podczas iteracji to: 0, 2, 5.

Należy zauważyć, że zmienna licznika w tym przykładzie jest zmienną lokalną. Po drugiej iteracji, która zwraca wartość 2, trzecia iteracja rozpoczyna się od miejsca, w którym poprzednio opuściła, zachowując poprzednią wartość zmiennej lokalnej o nazwie counter, która wynosiła 2.

Eranga Dissanayaka
źródło
11
Nie wyjaśniłeś, co to yield breakznaczy
Jay Sullivan
Nie sądzę, że yield returnfaktycznie obsługuje zwracanie wielu wartości. Może nie o to ci chodziło, ale tak to czytam.
Sam
Sam - metoda SampleNumbers z wielu wypowiedzi obie wydajność robi w rzeczywistości pracy, wartość iteratora jest zwracane niezwłocznie i wykonanie jest wznowione, gdy następna wartość jest wymagana. Widziałem, jak ludzie kończą taką metodę „podziałem zysków”, ale jest to niepotrzebne. Dotknięcie końca metody kończy także iterator
Loren Paulsen
powodem, dla którego jest to kiepski przykład, yield breakjest to, że nie zawiera on foreachmodułu wyliczającego na poziomie języka, takiego jak a - przy użyciu modułu wyliczającego yield breakzapewnia rzeczywistą wartość. ten przykład wygląda jak rozwinięta pętla. prawie nigdy nie zobaczysz tego kodu w prawdziwym świecie (wszyscy możemy myśleć o niektórych przypadkach brzegowych, oczywiście) również, nie ma tutaj „iteratora”. „blok iteratora” nie może wykraczać poza metodę ze względu na specyfikację języka. to, co faktycznie jest zwracane, jest „wyliczalne”, patrz także: stackoverflow.com/questions/742497/…
Shaun Wilson