2008-11-25 4 views
37

In meiner ewigen Suche nach weniger saugen Ich versuche, die "Ausbeute" Aussage zu verstehen, aber ich stoße immer wieder auf den gleichen Fehler.Einige helfen verstehen "Ausbeute"

Der Körper [somemethod] sein kann kein Iteratorblock weil 'System.Collections.Generic.List < AClass>' kein Iterator-Schnittstellentyp ist.

Dies ist der Code, wo ich stecken geblieben:

foreach (XElement header in headersXml.Root.Elements()){ 
    yield return (ParseHeader(header));     
} 

Was mache ich falsch? Kann ich den Ertrag nicht in einem Iterator verwenden? Was ist der Sinn? In diesem Beispiel wurde angegeben, dass List<ProductMixHeader> kein Iterator-Schnittstellentyp ist. ProductMixHeader ist eine benutzerdefinierte Klasse, aber ich stelle mir vor List ist ein Iterator-Interface-Typ, nein?

--Edit--
Danke für alle schnellen Antworten.
Ich weiß, diese Frage ist nicht so neu und die gleichen Ressourcen tauchen immer wieder auf.
Es stellte sich heraus, dass ich dachte, ich könnte List<AClass> als Rückgabetyp zurückgeben, aber seit List<T> ist nicht faul, kann es nicht. Ändern meiner Rückkehr Typ IEnumerable<T> hat das Problem gelöst: D

Eine etwas verwandte Frage (nicht wert, einen neuen Thread zu öffnen): ist es wert IEnumerable<T> als Rückgabetyp geben, wenn ich sicher bin, dass 99% der Fälle I‘ Ich werde gehen. ToList() sowieso? Was werden die Auswirkungen auf die Leistung sein?

+6

Ich mag den possitiven Ansatz 'Quest, weniger zu saugen' ;-). –

+0

Diese fast identische Frage hat einen Link zu einigen guten Raymond Chen Sachen: http://stackoverflow.com/questions/39476/what-is-the-yield-keyword-used-for-in-in-c –

Antwort

32

Verfahren yield return verwenden muss als zurückkehr eine der folgenden zwei Interfaces deklariert werden:

IEnumerable<SomethingAppropriate> 
IEnumerator<SomethingApropriate> 

(dank Jon und Marc für den Hinweis auf IEnumerator)

Beispiel:

public IEnumerable<AClass> YourMethod() 
{ 
    foreach (XElement header in headersXml.Root.Elements()) 
    { 
     yield return (ParseHeader(header));     
    } 
} 

Ausbeute ist ein fauler Produzent von Daten, produziert nur ein anderes Element af Nachdem die erste abgerufen wurde, wird bei der Rückgabe einer Liste alles auf einmal zurückgegeben.

Es gibt also einen Unterschied, und Sie müssen die Methode korrekt deklarieren. Weitere Informationen finden Sie unter Jon's answer here, die einige sehr nützliche Links enthält.

+2

Für den Rekord: oder IEnumerator [] –

+2

Es kann auch angegeben werden, IEnumerator oder IEnumerator zurückzugeben. –

+4

Darn es, geschlagen von 7 Sekunden;) –

0

Wie sieht die Methode aus, mit der Sie das verwenden? Ich denke nicht, dass dies allein in einer Schleife verwendet werden kann.

Zum Beispiel ...

public IEnumerable<string> GetValues() { 
    foreach(string value in someArray) { 
     if (value.StartsWith("A")) { yield return value; } 
    } 
} 
3

Liste implementiert IEnumerable.

Hier ist ein Beispiel, das etwas Licht auf das bringen könnte, was Sie versuchen zu lernen.Ich schrieb diese ca. 6 Monate

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace YieldReturnTest 
{ 
    public class PrimeFinder 
    { 
     private Boolean isPrime(int integer) 
     { 
      if (0 == integer) 
       return false; 

      if (3 > integer) 
       return true; 

      for (int i = 2; i < integer; i++) 
      { 
       if (0 == integer % i) 
        return false; 
      } 
      return true; 
     } 

     public IEnumerable<int> FindPrimes() 
     { 
      int i; 

      for (i = 1; i < 2147483647; i++) 
      { 
       if (isPrime(i)) 
       { 
        yield return i; 
       } 
      } 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      PrimeFinder primes = new PrimeFinder(); 

      foreach (int i in primes.FindPrimes()) 
      { 
       Console.WriteLine(i); 
       Console.ReadLine(); 
      } 

      Console.ReadLine(); 
      Console.ReadLine(); 
     } 
    } 
} 
8

„Ausbeute“ erzeugt einen Iteratorblock - ein Compiler erzeugte Klasse, die entweder IEnumerable[<T>] oder IEnumerator[<T>] implementieren kann. Jon Skeet hat eine sehr gute (und freie) Diskussion darüber in Kapitel 6 von C# in Depth.

Aber im Grunde - um "Ertrag" zu verwenden, muss Ihre Methode eine IEnumerable[<T>] oder IEnumerator[<T>] zurückgeben. In diesem Fall:

public IEnumerable<AClass> SomeMethod() { 
    // ... 
    foreach (XElement header in headersXml.Root.Elements()){ 
     yield return (ParseHeader(header));     
    } 
} 
+0

Danke! Es dachte, dass die Liste implementiert IEnumerable, aber es scheint nicht. Nun macht es Sinn, die extra Yield Meile zu gehen, wenn Sie SomeMethod(). ToList() ein paar Zeilen weiter auf der Straße? Wenn mir mein Verständnis recht ist, vereitelt es den ganzen Zweck der Ausbeute, richtig? –

+0

@boris - Liste implementiert IEnumerable - aber das ist nicht zu zeigen.Um einen Iterator-Block zu erstellen, müssen Sie entweder die I/O-Schnittstelle selbst zurückgeben. Es ist für nichts anderes definiert. –

+0

@boris - re die "besiegt den ganzen Zweck" - gar nicht; - p Es gibt eine Vielzahl von Anwendungen, in denen eine Streaming-API (wie IEnumerable ) vorzuziehen ist eine gepufferte Sammlung (z. B. Liste ) - vor allem wenn Sie beschäftigen sich mit vielen tausend Datensätzen (aus einer Datei oder Datenbank). –

14

Es ist ein heikles Thema. Kurz gesagt, es ist eine einfache Möglichkeit, IEnumerable und seine Freunde zu implementieren. Der Compiler erstellt Ihnen eine Zustandsmaschine, die Parameter und lokale Variablen in Instanzvariablen in einer neuen Klasse umwandelt. Kompliziertes Zeug.

Ich habe einige Ressourcen auf diesem:

+1

Großartiges Zeug! Es hat mir gefallen, wie Sie das Thema erklärt haben, indem Sie Listing 6.3 in Ihr Buch aufgenommen haben - das hat deutlich gemacht, was die Idee hinter Iteratoren und der Yield-Anweisung ist. Danke für das Teilen! – Matt

+0

Hallo Jon, der Link zu Kapitel 6 scheint vom Rand des Internets abgefallen zu sein, ich werde jetzt zu [Manning Publications] (https://www.manning.com/) umgeleitet, hast du einen alternativen Link? –

+1

@ LasseV.Karlsen: Ich habe den Link aktualisiert, um zur Seite C# in Depth zu gelangen, wo Sie das Kapitel selbst herunterladen können. –

3

ich sehr empfehlen die Verwendung von Reflector, um zu sehen, was yield tatsächlich für Sie tut. Sie werden in der Lage sein, den vollständigen Code der Klasse zu sehen, die der Compiler für Sie generiert, wenn Sie yield verwenden, und ich habe festgestellt, dass die Leute das Konzept viel schneller verstehen, wenn sie das Low-Level-Ergebnis sehen. level ich denke).

0

@Ian P's Antwort hat mir sehr geholfen, den Ertrag zu verstehen und warum er verwendet wird. Ein (großer) Anwendungsfall für den Ertrag ist in "foreach" -Schleifen nach dem Schlüsselwort "in", um eine vollständig vervollständigte Liste nicht zurückzugeben. Anstatt eine vollständige Liste auf einmal zurückzugeben, wird in jeder "foreach" -Schleife nur ein Element (das nächste Element) zurückgegeben. So werden Sie in solchen Fällen Rendite erzielen. Ich habe @Ian P's Code für mein besseres Verständnis der folgenden neu geschrieben:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace YieldReturnTest 
{ 
    public class PrimeFinder 
    { 
     private Boolean isPrime(int integer) 
     { 
      if (0 == integer) 
       return false; 

      if (3 > integer) 
       return true; 

      for (int i = 2; i < integer; i++) 
      { 
       if (0 == integer % i) 
        return false; 
      } 
      return true; 
     } 

     public IEnumerable<int> FindPrimesWithYield() 
     { 
      int i; 

      for (i = 1; i < 2147483647; i++) 
      { 
       if (isPrime(i)) 
       { 
        yield return i; 
       } 
      } 
     } 

     public IEnumerable<int> FindPrimesWithoutYield() 
     { 
      var primes = new List<int>(); 
      int i; 
      for (i = 1; i < 2147483647; i++) 
      { 
       if (isPrime(i)) 
       { 
        primes.Add(i); 
       } 
      } 
      return primes; 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      PrimeFinder primes = new PrimeFinder(); 

      Console.WriteLine("Finding primes until 7 with yield...very fast..."); 
      foreach (int i in primes.FindPrimesWithYield()) // FindPrimesWithYield DOES NOT iterate over all integers at once, it returns item by item 
      { 
       if (i > 7) 
       { 
        break; 
       } 
       Console.WriteLine(i); 
       //Console.ReadLine(); 

      } 

      Console.WriteLine("Finding primes until 7 without yield...be patient it will take lonkg time..."); 
      foreach (int i in primes.FindPrimesWithoutYield()) // FindPrimesWithoutYield DOES iterate over all integers at once, it returns the complete list of primes at once 
      { 
       if (i > 7) 
       { 
        break; 
       } 
       Console.WriteLine(i); 
       //Console.ReadLine(); 
      } 

      Console.ReadLine(); 
      Console.ReadLine(); 
     } 
    } 
} 
1

Um zu verstehen, yield, müssen Sie verstehen, wenn IEnumerator und IEnumerable verwenden (weil Sie entweder von ihnen verwenden) . Die folgenden Beispiele helfen Ihnen, den Unterschied zu verstehen.

Betrachten Sie zuerst die folgende Klasse, es implementiert zwei Methoden - eine, die IEnumerator<int> zurückgibt, eine, die IEnumerable<int> zurückgibt. Ich werde Ihnen zeigen, dass es ein großer Unterschied in der Nutzung ist, obwohl der Code der 2 Methoden sucht ähnlich:

// 2 iterators, one as IEnumerator, one as IEnumerable 
public class Iterator 
{ 
    public static IEnumerator<int> IterateOne(Func<int, bool> condition) 
    { 
     for(var i=1; condition(i); i++) { yield return i; }  
    } 
    public static IEnumerable<int> IterateAll(Func<int, bool> condition) 
    { 
     for(var i=1; condition(i); i++) { yield return i; }  
    } 
} 

Nun, wenn Sie IterateOne verwenden Sie folgendes tun:

// 1. Using IEnumerator allows to get item by item 
    var i=Iterator.IterateOne(x => true); // iterate endless 
    // 1.a) get item by item 
    i.MoveNext(); Console.WriteLine(i.Current); 
    i.MoveNext(); Console.WriteLine(i.Current); 
    // 1.b) loop until 100 
    int j; while (i.MoveNext() && (j=i.Current)<=100) { Console.WriteLine(j); } 

1.a) druckt:

1
2

1.b) druckt:

3
4
...
100

, weil sie zu zählen weiterhin direkt nach dem 1.a) Anweisungen ausgeführt wurden.

Sie können sehen, dass Sie Artikel für Artikel mit MoveNext() weiterleiten können.


Im Gegensatz dazu IterateAll können Sie foreach und auch LINQ Aussagen für größere Bequemlichkeit verwenden:

// 2. Using IEnumerable makes looping and LINQ easier 
    var k=Iterator.IterateAll(x => x<100); // limit iterator to 100 
    // 2.a) Use a foreach loop 
    foreach(var x in k){ Console.WriteLine(x); } // loop 
    // 2.b) LINQ: take 101..200 of endless iteration 
    var lst=Iterator.IterateAll(x=>true).Skip(100).Take(100).ToList(); // LINQ: take items 
    foreach(var x in lst){ Console.WriteLine(x); } // output list 

2.a) druckt:

1
2
. ..
99

2.b) druckt:

101
102
...
200


Hinweis: Seit IEnumerator<T> und IEnumerable<T> Generics sind, können sie sein mit jedem Typ verwendet. Aus Gründen der Einfachheit habe ich jedoch int in meinen Beispielen für den Typ T verwendet.

Das bedeutet, Sie können einen der Rückgabetypen IEnumerator<ProductMixHeader> oder IEnumerable<ProductMixHeader> (die benutzerdefinierte Klasse, die Sie in Ihrer Frage erwähnt haben) verwenden. Der Typ List<ProductMixHeader> implementiert keine dieser Schnittstellen, weshalb Sie ihn nicht so verwenden können. Aber Beispiel 2.b) zeigt, wie Sie daraus eine Liste erstellen können.