2010-03-16 3 views
32

Diese beiden Methoden scheinen mir gleich zu verhaltenIst yield break Äquivalent Enumerable <T> .Empty von einer Methode zur Rückkehr IEnumerable Rückkehr <T>

public IEnumerable<string> GetNothing() 
{ 
    return Enumerable.Empty<string>(); 
} 

public IEnumerable<string> GetLessThanNothing() 
{ 
    yield break; 
} 

ich je in Testszenarien profiliert haben und ich sehe nicht, ein bedeutender Unterschied in der Geschwindigkeit, aber die yield break Version ist etwas schneller.

Gibt es Gründe, einen über den anderen zu verwenden? Ist einer leichter zu lesen als der andere? Gibt es einen Verhaltensunterschied, der für einen Anrufer von Bedeutung wäre?

Antwort

30

Wenn Sie immer ein leeres Aufzählungszeichen zurückgeben möchten, dann ist die Verwendung der Enumerable.Empty<string>() Syntax deklarativer IMHO.

Der Leistungsunterschied hier ist fast sicher nicht signifikant. Ich würde mich hier auf Lesbarkeit konzentrieren, bis ein Profiler Ihnen gezeigt hat, dass es ein Problem ist.

+2

Hier und überall sonst (außer ein Profiler sagt etwas anderes) –

1

Es würde scheinen, dass mindestens ein weniger Objekt instanziieren würde, als was return Enumerable.Empty<string>() tun würde. Darüber hinaus kann es einige Überprüfungen geben, die Sie mit yield break kurzschließen würden. Und wenn nichts anderes, es ist ein weniger Funktions-Wrapper, den Ihr Stapel durchläuft, der zwar feststellbar aber nicht bemerkbar wäre.

Allerdings stimme ich mit der anderen Antwort gepostet, dass .Empty ist der "bevorzugte" Weg, dies zu tun.

+8

Warum würden Sie das denken? 'Enumerable.Empty ' könnte jedes Mal die gleiche Sache zurückgeben - und ich glaube, dass es tatsächlich ist. Nach dem ersten Anruf muss nichts mehr erstellt werden. Ich bezweifle sehr, dass der C# -Compiler sehen würde, dass er das für den Fall des "Renderns" tun könnte. –

+1

@ Jon Skeet - Du hast Recht. Die Yield-Break-Version instanziiert tatsächlich die Klasse "Yield", die jedes Mal generiert wird. 'Enumerable.Empty' ist intelligent genug, um –

+0

zwischenzuspeichern. Müsste es nicht mindestens eine geklonte Kopie (d. H. Eine neue Instanziierung) zurückgeben? – Jaxidian

9

IEnumerable<T> Methoden mit yield break oder yield return in ihren Körpern wird in State-Maschinen umgewandelt. Bei dieser Art von Methoden können Sie Renditen nicht mit traditionellen Renditen kombinieren. Was ich meine ist, dass, wenn Sie etwas in einem Teil der Methode liefern, Sie eine ICollection in einem anderen nicht zurückgeben können.

Angenommen, Sie implementieren eine Methode mit dem Rückgabetyp IEnumerable<T>, indem Sie Elemente zu einer Sammlung hinzufügen und dann eine schreibgeschützte Kopie der Sammlung zurückgeben. Wenn Sie aus irgendeinem Grund eine leere Sammlung zurückgeben möchten, können Sie keine yield break. Alles, was Sie tun können, ist einfach Enumerable.Empty<T>() zurückgeben.

Wenn Sie in beiden Richtungen profiliert haben, und es gibt keine signifikante Veränderung, dann kann man es einfach vergessen :)

+0

+1 für die Klärung des Generic-Enumerable/Yield-Ansatzes vs. der Sammlungsaufbau-Methode, d. H. Jeder Ansatz * zwingt * einen Grund, das eine oder das andere zu verwenden. – downwitch

+0

Id sagen IEnumerable Renditen verhalten sich wie "Python Generatoren, die wiederholt werden können". Der Vorteil von IEnumerable besteht darin, MemLoad nicht die gesamte Liste oder das Ergebnis auf einmal zu laden. Daher ist die Verwendung von Erträgen konsistent effizienter als das Erstellen einer vollständigen Liste oder eines Arrays und das Zurückgeben derselben – Felype

5

Komisch, ich diesen Beitrag heute Morgen gelesen, und wenige Stunden später ich damit getroffen wurde Beispiel - fand den Unterschied, wenn Sie mehr Code haben:

public static IEnumerable<T> CoalesceEmpty<T>(IEnumerable<T> coll) 
{ 
    if (coll == null) 
     return Enumerable.Empty<T>(); 
    else 
     return coll; 
} 

Sie können nicht die erste Rückkehr ändern Pause zu erhalten, weil Sie müssten auch die zweite Rückkehr ändern (längere Version).

5

Ich habe jede in Testszenarien profiliert und ich sehe keinen sinnvollen Unterschied in der Geschwindigkeit, aber die Ausbeute brechen Version ist etwas schneller.

Ich gehe davon aus, dass Ihre Profiling-Tests Programmstartgeschwindigkeit nicht enthalten. Das Konstrukt yield funktioniert, indem es eine Klasse für Sie generiert. Dieser zusätzliche Code ist ideal, wenn er die benötigte Logik bereitstellt. Wenn dies nicht der Fall ist, werden lediglich die Festplatten-E/A, die Größe des Arbeitssatzes und die JIT-Zeit hinzugefügt.

Wenn Sie ein Programm mit Ihrer Testmethode in ILSpy öffnen und enumerator Dekompilierungsprozeß deaktivieren, haben Sie eine Klasse mit dem Namen <GetLessThanNothing>d__0 mit einem Dutzend oder so Mitgliedern finden. Seine MoveNext Methode sieht wie folgt aus:

bool IEnumerator.MoveNext() 
{ 
    int num = this.<>1__state; 
    if (num == 0) 
    { 
     this.<>1__state = -1; 
    } 
    return false; 
} 

EmptyEnumerable Werke von lazily ein statisches leeres Array zu erstellen. Vielleicht ist der Grund, EmptyEnumerable zu prüfen, ob das Array erstellt werden muss, langsamer als yield break im isolierten Benchmarking, aber es würde wahrscheinlich eine ganze Reihe von Iterationen dauern, um den Startup-Penalty zu überwinden, und beide würden wahrscheinlich insgesamt nicht wahrnehmbar sein ein "Tod durch tausend Perf Papercuts" -Szenario.

Verwandte Themen