Wofür wird in C # das Schlüsselwortield verwendet?

828

In der Frage Wie kann ich nur ein Fragment von IList <> verfügbar machen? Hatte eine der Antworten den folgenden Codeausschnitt:

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

Was macht das Yield-Keyword dort? Ich habe gesehen, dass es an einigen Stellen referenziert wurde, und eine andere Frage, aber ich habe nicht ganz herausgefunden, was es tatsächlich tut. Ich bin es gewohnt, an Ausbeute im Sinne eines Threads zu denken, der einem anderen nachgibt, aber das scheint hier nicht relevant zu sein.

Herms
quelle
Nur MSDN Link darüber ist hier msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx
Entwickler
14
Das ist nicht überraschend. Die Verwirrung kommt von der Tatsache, dass wir konditioniert sind, um "return" als Funktionsausgabe zu sehen, während eine "Ausbeute" vorausgeht, die es nicht ist.
Larry
4
Ich habe die Dokumente gelesen, aber ich fürchte, ich verstehe immer noch nicht :(
Ortund

Antworten:

737

Das yieldSchlüsselwort macht hier tatsächlich ziemlich viel.

Die Funktion gibt ein Objekt zurück, das die IEnumerable<object>Schnittstelle implementiert . Wenn eine aufrufende Funktion foreachüber diesem Objekt beginnt , wird die Funktion erneut aufgerufen, bis sie "nachgibt". Dies ist syntaktischer Zucker, der in C # 2.0 eingeführt wurde . In früheren Versionen mussten Sie Ihre eigenen IEnumerableund IEnumeratorObjekte erstellen, um solche Dinge zu tun.

Der einfachste Weg, Code wie diesen zu verstehen, besteht darin, ein Beispiel einzugeben, einige Haltepunkte festzulegen und zu sehen, was passiert. Versuchen Sie, dieses Beispiel durchzugehen:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

Wenn Sie durch das Beispiel Schritt, werden Sie den ersten Anruf finden Integers()kehrt 1. Der zweite Aufruf kehrt zurück 2und die Leitung yield return 1wird nicht erneut ausgeführt.

Hier ist ein Beispiel aus dem wirklichen Leben:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}
Mendelt
quelle
113
In diesem Fall wäre das einfacher. Ich verwende hier nur die Ganzzahl, um zu zeigen, wie die Rendite funktioniert. Das Schöne an der Verwendung von Yield Return ist, dass es eine sehr schnelle Möglichkeit ist, das Iteratormuster zu implementieren, sodass die Dinge träge bewertet werden.
Mendelt
111
Erwähnenswert ist auch, dass Sie verwenden können, yield break;wenn Sie keine weiteren Artikel zurückgeben möchten.
Rory
7
yieldist kein Schlüsselwort. Wenn es so wäre, könnte ich Yield nicht als Kennung wie inint yield = 500;
Brandin
5
@Brandin, weil alle Programmiersprachen zwei Arten von Schlüsselwörtern unterstützen, nämlich reservierte und kontextbezogene. Die Rendite fällt in die spätere Kategorie, weshalb Ihr Code vom C # -Compiler nicht verboten wird. Weitere Details hier: ericlippert.com/2009/05/11/reserved-and-contextual-keywords Sie würden sich freuen zu wissen, dass es auch reservierte Wörter gibt, die von einer Sprache nicht als Schlüsselwörter erkannt werden. Zum Beispiel gehe in Java. Weitere Details hier: stackoverflow.com/questions/2545103/…
RBT
7
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'. klingt für mich nicht richtig Ich habe immer an das Schlüsselwort c #ield im Zusammenhang mit "Die Ernte bringt eine reichliche Ernte" statt an "Das Auto gibt dem Fußgänger Erträge" gedacht.
Zack
369

Wiederholung. Es erstellt eine Zustandsmaschine "unter der Decke", die sich merkt, wo Sie sich in jedem zusätzlichen Zyklus der Funktion befanden, und von dort aus aufnimmt.

Joel Coehoorn
quelle
210

Ertrag hat zwei großartige Verwendungszwecke:

  1. Es ist hilfreich, eine benutzerdefinierte Iteration bereitzustellen, ohne temporäre Sammlungen zu erstellen.

  2. Es hilft, eine zustandsbehaftete Iteration durchzuführen. Geben Sie hier die Bildbeschreibung ein

Um die beiden obigen Punkte anschaulicher zu erläutern, habe ich ein einfaches Video erstellt, das Sie hier ansehen können

Shivprasad Koirala
quelle
13
Das Video hilft mir, das klar zu verstehen yield. @ ShivprasadKoiralas Code-Projektartikel Was nützt C # Yield? der gleichen Erklärung ist auch eine gute Quelle
Dush
Ich würde auch als dritten Punkt hinzufügen, dass dies yieldeine "schnelle" Möglichkeit ist, einen benutzerdefinierten IEnumerator zu erstellen (statt dass eine Klasse die IEnumerator-Schnittstelle implementiert).
MrTourkos
Ich habe Ihr Video Shivprasad gesehen und es hat die Verwendung des Yield-Keywords deutlich gemacht.
Tore Aurstad
Danke für das Video!. Sehr gut erklärt!
Roblogic
Tolles Video, aber ich frage mich ... Die Implementierung mit Yield ist offensichtlich sauberer, aber es muss im Wesentlichen ein eigener temporärer Speicher oder / und eine eigene Liste intern erstellt werden, um den Status zu verfolgen (oder vielmehr eine Statusmaschine zu erstellen). Tut "Yield" also etwas anderes, als die Implementierung zu vereinfachen und die Dinge besser aussehen zu lassen, oder steckt noch etwas anderes dahinter? Wie wäre es mit Effizienz? Ist das Ausführen von Code mit Yield mehr oder weniger effizient / schnell als ohne?
harte Fragen
135

Kürzlich hat Raymond Chen auch eine interessante Artikelserie zum Ertragsschlüsselwort veröffentlicht.

Es wird zwar nominell zur einfachen Implementierung eines Iteratormusters verwendet, kann aber zu einer Zustandsmaschine verallgemeinert werden. Es macht keinen Sinn, Raymond zu zitieren, der letzte Teil verweist auch auf andere Verwendungen (aber das Beispiel in Entins Blog ist besonders gut und zeigt, wie man asynchronen sicheren Code schreibt).

Svend
quelle
Dies muss abgestimmt werden. Süß, wie er den Zweck des Bedieners und der Einbauten erklärt.
Sajidnizami
3
Teil 1 erklärt den syntaktischen Zucker der "Ertragsrendite". ausgezeichnete Erklärung!
Dror Weiss
99

Auf den ersten Blick ist Yield Return ein .NET- Zucker, um eine IEnumerable zurückzugeben .

Ohne Ertrag werden alle Elemente der Sammlung auf einmal erstellt:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

Gleicher Code mit Ertrag, der Artikel für Artikel zurückgibt:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

Der Vorteil der Verwendung von Yield besteht darin, dass der Rest der Elemente nicht erstellt wird, wenn die Funktion, die Ihre Daten verbraucht, lediglich das erste Element der Sammlung benötigt.

Der Ertragsoperator ermöglicht die Erstellung von Artikeln nach Bedarf. Das ist ein guter Grund, es zu benutzen.

Marquinho Peli
quelle
40

yield returnwird mit Enumeratoren verwendet. Bei jedem Aufruf der Yield-Anweisung wird die Kontrolle an den Anrufer zurückgegeben, es wird jedoch sichergestellt, dass der Status des Angerufenen beibehalten wird. Wenn der Aufrufer das nächste Element auflistet, setzt er daher die Ausführung in der callee-Methode ab Anweisung unmittelbar nach der yieldAnweisung fort.

Versuchen wir dies anhand eines Beispiels zu verstehen. In diesem Beispiel habe ich entsprechend jeder Zeile die Reihenfolge erwähnt, in der die Ausführung erfolgt.

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

Außerdem wird der Status für jede Aufzählung beibehalten. Angenommen, ich habe einen weiteren Aufruf der Fibs()Methode, dann wird der Status dafür zurückgesetzt.

RKS
quelle
2
setze prevFib = 1 - die erste Fibonacci-Zahl ist eine "1", keine "0"
fubo
31

Intuitiv gibt das Schlüsselwort einen Wert aus der Funktion zurück, ohne ihn zu verlassen, dh in Ihrem Codebeispiel gibt es den aktuellen itemWert zurück und setzt dann die Schleife fort. Formal wird es vom Compiler verwendet, um Code für einen Iterator zu generieren . Iteratoren sind Funktionen, die IEnumerableObjekte zurückgeben. Das MSDN enthält mehrere Artikel darüber.

Konrad Rudolph
quelle
4
Um genau zu sein, wird die Schleife nicht fortgesetzt, sondern angehalten, bis das übergeordnete Element "iterator.next ()" aufruft.
Alex
8
@jitbit Deshalb habe ich "intuitiv" und "formeller" verwendet.
Konrad Rudolph
31

Eine Listen- oder Array-Implementierung lädt alle Elemente sofort, während die Yield-Implementierung eine verzögerte Ausführungslösung bietet.

In der Praxis ist es häufig wünschenswert, den Mindestarbeitsaufwand nach Bedarf auszuführen, um den Ressourcenverbrauch einer Anwendung zu reduzieren.

Beispielsweise haben wir möglicherweise eine Anwendung, die Millionen von Datensätzen aus einer Datenbank verarbeitet. Die folgenden Vorteile können erzielt werden, wenn IEnumerable in einem Pull-basierten Modell mit verzögerter Ausführung verwendet wird:

  • Die Skalierbarkeit, Zuverlässigkeit und Vorhersagbarkeit werden sich wahrscheinlich verbessern, da die Anzahl der Datensätze die Ressourcenanforderungen der Anwendung nicht wesentlich beeinflusst.
  • Leistung und Reaktionsfähigkeit werden sich wahrscheinlich verbessern, da die Verarbeitung sofort beginnen kann, anstatt darauf zu warten, dass die gesamte Sammlung zuerst geladen wird.
  • Die Wiederherstellbarkeit und Auslastung wird sich wahrscheinlich verbessern, da die Anwendung gestoppt, gestartet, unterbrochen oder fehlgeschlagen werden kann. Im Vergleich zum Vorabrufen aller Daten, bei denen tatsächlich nur ein Teil der Ergebnisse verwendet wurde, gehen nur die laufenden Elemente verloren.
  • In Umgebungen, in denen konstante Workload-Streams hinzugefügt werden, ist eine kontinuierliche Verarbeitung möglich.

Hier ist ein Vergleich zwischen dem ersten Erstellen einer Sammlung, z. B. einer Liste, und dem Verwenden von Yield.

Listenbeispiel

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

Konsolenausgabe
ContactListStore: Erstellen eines Kontakts 1
ContactListStore: Erstellen eines Kontakts 2
ContactListStore: Erstellen eines Kontakts 3
Bereit zum Durchlaufen der Sammlung.

Hinweis: Die gesamte Sammlung wurde in den Speicher geladen, ohne nach einem einzelnen Element in der Liste zu fragen

Ertragsbeispiel

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

Konsolenausgabe
Bereit zum Durchlaufen der Sammlung.

Hinweis: Die Sammlung wurde überhaupt nicht ausgeführt. Dies ist auf die "verzögerte Ausführung" von IEnumerable zurückzuführen. Das Erstellen eines Elements erfolgt nur, wenn es wirklich benötigt wird.

Rufen wir die Sammlung erneut auf und beobachten das Verhalten, wenn wir den ersten Kontakt in der Sammlung abrufen.

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

Konsolenausgabe
Bereit zum Durchlaufen der Sammlung
ContactYieldStore: Erstellen von Kontakt 1
Hallo Bob

Nett! Nur der erste Kontakt wurde erstellt, als der Kunde den Artikel aus der Sammlung "zog".

Strydom
quelle
1
Diese Antwort braucht mehr Aufmerksamkeit! Thx
leon22
@ leon22 absolut +2
snr
26

Hier ist ein einfacher Weg, um das Konzept zu verstehen: Die Grundidee ist, wenn Sie eine Sammlung möchten, für die Sie " foreach" verwenden können, das Sammeln der Elemente in der Sammlung jedoch aus irgendeinem Grund teuer ist (z. B. das Abfragen aus einer Datenbank). UND Sie benötigen häufig nicht die gesamte Sammlung. Anschließend erstellen Sie eine Funktion, mit der die Sammlung einzeln erstellt und an den Verbraucher zurückgegeben wird (der den Sammlungsaufwand dann vorzeitig beenden kann).

Stellen Sie sich das so vor: Sie gehen zur Fleischtheke und möchten ein Pfund geschnittenen Schinken kaufen. Der Metzger nimmt einen 10-Pfund-Schinken nach hinten, legt ihn auf die Schneidemaschine, schneidet das Ganze in Scheiben, bringt dann den Scheibenhaufen zu Ihnen zurück und misst ein Pfund davon ab. (Alter Weg). Mit yieldbringt der Metzger die Schneidemaschine zur Theke und beginnt, jede Scheibe auf der Waage zu schneiden und "nachzugeben", bis sie 1 Pfund misst. Dann wickelt er sie für Sie ein und Sie sind fertig. Der alte Weg mag für den Metzger besser sein (lässt ihn seine Maschinen so organisieren, wie er möchte), aber der neue Weg ist in den meisten Fällen für den Verbraucher deutlich effizienter.

kmote
quelle
18

Mit dem yieldSchlüsselwort können Sie ein IEnumerable<T>Formular in einem Iteratorblock erstellen . Dieser Iteratorblock unterstützt die verzögerte Ausführung. Wenn Sie mit dem Konzept nicht vertraut sind, kann es fast magisch erscheinen. Letztendlich ist es jedoch nur Code, der ohne seltsame Tricks ausgeführt wird.

Ein Iteratorblock kann als syntaktischer Zucker beschrieben werden, bei dem der Compiler eine Zustandsmaschine generiert, die verfolgt, wie weit die Aufzählung der Aufzählung fortgeschritten ist. Um eine Aufzählung aufzulisten, verwenden Sie häufig eine foreachSchleife. Eine foreachSchleife ist jedoch auch syntaktischer Zucker. Sie sind also zwei Abstraktionen, die aus dem realen Code entfernt wurden, weshalb es anfangs schwierig sein kann zu verstehen, wie alles zusammenarbeitet.

Angenommen, Sie haben einen sehr einfachen Iteratorblock:

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

Echte Iteratorblöcke haben häufig Bedingungen und Schleifen, aber wenn Sie die Bedingungen überprüfen und die Schleifen abrollen, werden sie immer noch als yieldAnweisungen ausgegeben, die mit anderem Code verschachtelt sind.

Um den Iteratorblock aufzulisten, wird eine foreachSchleife verwendet:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

Hier ist die Ausgabe (keine Überraschungen hier):

Start
1
Nach 1
2
Nach 2
42
Ende

Wie oben angegeben foreachist syntaktischer Zucker:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

Um dies zu entwirren, habe ich ein Sequenzdiagramm mit den entfernten Abstraktionen erstellt:

C # -Iteratorblocksequenzdiagramm

Die vom Compiler generierte Zustandsmaschine implementiert auch den Enumerator, aber um das Diagramm klarer zu machen, habe ich sie als separate Instanzen gezeigt. (Wenn die Zustandsmaschine von einem anderen Thread aufgezählt wird, erhalten Sie tatsächlich separate Instanzen, aber dieses Detail ist hier nicht wichtig.)

Jedes Mal, wenn Sie Ihren Iteratorblock aufrufen, wird eine neue Instanz der Zustandsmaschine erstellt. Ihr Code im Iteratorblock wird jedoch erst ausgeführt, wenn er enumerator.MoveNext()zum ersten Mal ausgeführt wird. So funktioniert die verzögerte Ausführung. Hier ist ein (ziemlich dummes) Beispiel:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

Zu diesem Zeitpunkt wurde der Iterator noch nicht ausgeführt. Die WhereKlausel erstellt eine neue IEnumerable<T>, die die IEnumerable<T>zurückgegebenen von IteratorBlockumschließt, aber diese Aufzählung muss noch aufgelistet werden. Dies geschieht, wenn Sie eine foreachSchleife ausführen :

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

Wenn Sie die Aufzählung zweimal auflisten, wird jedes Mal eine neue Instanz der Zustandsmaschine erstellt, und Ihr Iteratorblock führt denselben Code zweimal aus.

Beachten Sie, dass LINQ Methoden wie ToList(), ToArray(), First(), Count()wird usw. , eine Verwendung foreachSchleife die enumerable aufzuzählen. Zum Beispiel ToList()werden alle Elemente der Aufzählung aufgelistet und in einer Liste gespeichert. Sie können jetzt auf die Liste zugreifen, um alle Elemente der Aufzählung abzurufen, ohne dass der Iteratorblock erneut ausgeführt wird. Es gibt einen Kompromiss zwischen der Verwendung der CPU, um die Elemente der Aufzählung mehrfach zu erzeugen, und dem Speicher, um die Elemente der Aufzählung zu speichern, um mehrmals auf sie zuzugreifen, wenn Methoden wie verwendet werden ToList().

Martin Liversage
quelle
18

Wenn ich das richtig verstehe, würde ich dies aus der Perspektive der Funktion, die IEnumerable mit Ertrag implementiert, folgendermaßen formulieren.

  • Hier ist eine.
  • Rufen Sie erneut an, wenn Sie einen anderen benötigen.
  • Ich werde mich erinnern, was ich dir schon gegeben habe.
  • Ich werde nur wissen, ob ich Ihnen noch einen geben kann, wenn Sie erneut anrufen.
Casey Plummer
quelle
einfach & brillant
Harry
10

Das C # Yield-Schlüsselwort ermöglicht, um es einfach auszudrücken, viele Aufrufe eines Code-Körpers, der als Iterator bezeichnet wird, der weiß, wie er zurückkehrt, bevor er fertig ist, und bei einem erneuten Aufruf dort fortgesetzt wird, wo er aufgehört hat - dh einem Iterator hilft Werden Sie für jedes Element in einer Sequenz, die der Iterator in aufeinanderfolgenden Aufrufen zurückgibt, transparent.

In JavaScript wird das gleiche Konzept als Generatoren bezeichnet.

BoiseBaked
quelle
Beste Erklärung bisher. Sind das auch die gleichen Generatoren in Python?
Petrosmm
7

Es ist eine sehr einfache und einfache Möglichkeit, eine Aufzählung für Ihr Objekt zu erstellen. Der Compiler erstellt eine Klasse, die Ihre Methode umschließt und in diesem Fall IEnumerable <Objekt> implementiert. Ohne das Schlüsselwort yield müssten Sie ein Objekt erstellen, das IEnumerable <Objekt> implementiert.


quelle
5

Es produziert unzählige Sequenzen. Tatsächlich wird eine lokale IEnumerable-Sequenz erstellt und als Methodenergebnis zurückgegeben

aku
quelle
3

Dieser Link hat ein einfaches Beispiel

Noch einfachere Beispiele finden Sie hier

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Beachten Sie, dass die Rendite von der Methode nicht zurückgegeben wird. Sie können sogar eine WriteLinenach dem setzenyield return

Das Obige ergibt eine IE-Zahl von 4 Zoll 4,4,4,4

Hier mit einem WriteLine. Fügt der Liste 4 hinzu, druckt abc, fügt dann 4 zur Liste hinzu, vervollständigt dann die Methode und kehrt so wirklich von der Methode zurück (sobald die Methode abgeschlossen ist, wie es bei einer Prozedur ohne Rückgabe der Fall wäre). Dies hätte jedoch einen Wert, eine IEnumerableListe von ints, die nach Abschluss zurückgegeben wird.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

Beachten Sie auch, dass das, was Sie zurückgeben, bei Verwendung vonield nicht vom gleichen Typ wie die Funktion ist. Es ist vom Typ eines Elements in der IEnumerableListe.

Sie verwenden Yield mit dem Rückgabetyp der Methode als IEnumerable. Wenn der Rückgabetyp der Methode intoder ist List<int>und Sie verwenden yield, wird sie nicht kompiliert. Sie können den IEnumerableMethodenrückgabetyp ohne Ertrag verwenden, aber es scheint, dass Sie den Ertrag ohne IEnumerableMethodenrückgabetyp nicht verwenden können .

Und um es auszuführen, müssen Sie es auf besondere Weise aufrufen.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}
Barlop
quelle
Hinweis: Wenn Sie versuchen, SelectMany zu verstehen, werden Ertrag und auch Generika verwendet. Dieses Beispiel kann helfen public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }und public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
barlop
Sieht nach einer sehr guten Erklärung aus! Dies könnte die akzeptierte Antwort gewesen sein.
Pongapundit
@pongapundit danke, meine Antwort ist sicherlich klar und einfach, aber ich habe selbst nicht viel verwendet, andere Antwortende haben viel mehr Erfahrung damit und wissen um ihre Verwendung als ich. Was ich hier über Ertrag geschrieben habe, war wahrscheinlich, dass ich mir am Kopf gekratzt habe, um einige der Antworten hier und unter diesem dotnetperls-Link herauszufinden! Aber da ich das nicht gut weiß yield return(abgesehen von der einfachen Sache, die ich erwähnt habe) und es nicht viel benutzt habe und nicht viel über seine Verwendung weiß, denke ich nicht, dass dies die akzeptierte sein sollte.
Barlop
3

Ein wichtiger Punkt beim Yield-Schlüsselwort ist Lazy Execution . Mit Lazy Execution meine ich nun, dass es bei Bedarf ausgeführt wird. Ein besserer Weg, es auszudrücken, ist ein Beispiel zu geben

Beispiel: Ertrag nicht verwenden, dh keine verzögerte Ausführung.

        public static IEnumerable<int> CreateCollectionWithList()
        {
            var list =  new List<int>();
            list.Add(10);
            list.Add(0);
            list.Add(1);
            list.Add(2);
            list.Add(20);

            return list;
        }

Beispiel: Verwenden von Yield, dh Lazy Execution.

    public static IEnumerable<int> CreateCollectionWithYield()
    {
        yield return 10;
        for (int i = 0; i < 3; i++) 
        {
            yield return i;
        }

        yield return 20;
    }

Nun, wenn ich beide Methoden aufrufe.

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

Sie werden feststellen, dass listItems 5 Elemente enthält (bewegen Sie die Maus beim Debuggen über listItems). Während YieldItems nur einen Verweis auf die Methode und nicht auf die Items haben. Das bedeutet, dass der Prozess zum Abrufen von Elementen innerhalb der Methode nicht ausgeführt wurde. Eine sehr effiziente Möglichkeit, Daten nur bei Bedarf abzurufen. Die tatsächliche Umsetzung der Rendite kann in ORM wie Entity Framework und NHibernate usw. gesehen werden.

maxspan
quelle
-3

Es wird versucht, Ruby Goodness einzubringen :)
Konzept: Dies ist ein Beispiel für Ruby-Code, der jedes Element des Arrays ausdruckt

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

Das Array ist jede Methode Implementierung Ausbeuten an den Aufrufer Kontrolle über (die ‚setzt x‘) mit jedem Element des Arrays ordentlich als x dargestellt. Der Anrufer kann dann mit x alles tun, was er braucht.

Allerdings .Net geht nicht den ganzen Weg hier .. C # mit IEnumerable zu haben gekoppelt Ausbeute scheint, in einer Art und Weise zwingt Sie eine foreach - Schleife in dem Anrufer zu schreiben , wie in Mendelt Antwort gesehen. Etwas weniger elegant.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}
Gishu
quelle
7
-1 Diese Antwort klingt für mich nicht richtig. Ja, C # yieldist mit gekoppelt IEnumerable, und C # fehlt das Ruby-Konzept eines "Blocks". Aber C # hat Lambdas, die die Implementierung einer ForEachMethode ermöglichen könnten , ähnlich wie bei Ruby each. Dies bedeutet jedoch nicht, dass dies eine gute Idee wäre.
Rsenna
Besser noch: public IEnumerable <int> Each () {int index = 0; Ertragsrückgabedaten [Index ++]; }
ata