2010-11-12 10 views
5

Ich versuche, meinen Kopf darum zu wickeln, was der C# -Compiler tut, wenn ich Linq-Methoden verkette, insbesondere wenn ich die gleiche Methode mehrfach verkette.Verstehen, wie der C# -Compiler die Verkettung von Linq-Methoden behandelt

Einfaches Beispiel: Angenommen, ich versuche, eine Sequenz von Ints basierend auf zwei Bedingungen zu filtern.

Die naheliegendste Sache ist, etwas zu tun, wie folgt aus:

IEnumerable<int> Method1(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0 && i % 5 == 0); 
} 

Aber wir konnte Kette auch in dem Verfahren mit einer einzigen Bedingung in jeder:

IEnumerable<int> Method2(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0); 
} 

ich hatte Schau dir die IL in Reflector an. es ist offensichtlich für die beiden Methoden, aber im Moment jenseits meinem Wissen weiter analysiert :)

Ich mag würde, um herauszufinden:
a) was der Compiler in jedem Fall anders macht, und warum.
b) gibt es keine Auswirkungen auf die Leistung (nicht auf Mikro-optimize versucht;! Nur neugierig)

Antwort

9

Die Antwort auf (a) ist kurz, aber ich werde weiter unten mehr ins Detail gehen:

Der Compiler nicht wirklich tun, um die Verkettungs - es zur Laufzeit geschieht, die normalen Organisation der Objekte! Es gibt viel weniger Magie als das, was auf den ersten Blick erscheinen mag - Jon Skeet recently completed the "Where clause" step in seiner Blog-Serie, LINQ to Objects neu implementieren. Ich würde empfehlen, das durchzulesen.

In sehr kurzen Laufzeiten, was passiert, ist dies: jedes Mal, wenn Sie die Methode Where Erweiterung nennen, es gibt ein neues WhereEnumerable Objekt, das zwei Dinge hat - einen Verweis auf den vorherigen IEnumerable (die, die Sie Where auf genannt) und das Lambda, das du zur Verfügung gestellt hast.

Wenn Sie über diese WhereEnumerable starten Iterieren (zum Beispiel in einem foreach später unten im Code), intern einfach es beginnt auf der IEnumerable Iterieren dass es verwiesen hat.

„Die foreach fragte mich nur für das nächste Element in meiner Sequenz, so um ich drehe und auf das nächste Element in Ihre Sequenz zu fragen“.

Das geht den ganzen Weg entlang der Kette, bis wir den Ursprung treffen, der eigentlich eine Art Array oder Speicherung von realen Elementen ist.Da jedes Enumerable dann "OK, hier ist mein Element" sagt, gibt es die Kette wieder hoch, es wendet auch seine eigene Logik an. Für eine Where wendet es das Lambda an, um festzustellen, ob das Element die Kriterien erfüllt. Wenn dies der Fall ist, kann es zum nächsten Anrufer weitergehen. Wenn dies fehlschlägt, stoppt es an diesem Punkt, kehrt zu seiner referenzierten Enumerable zurück und fragt nach dem nächsten Element.

Das passiert so lange, bis alle MoveNext false zurückgibt, was bedeutet, dass die Aufzählung abgeschlossen ist und es keine Elemente mehr gibt.

zu beantworten (b), gibt es immer einen Unterschied, aber hier ist es viel zu trivial stören mit. Mach dir keine Sorgen :)

+0

Schöne Antwort. Wir brauchen mehr Material wie dieses auf Stackoverflow. –

1
  1. Die erste Iterator verwenden, wird die zweite zwei verwenden. Das heißt, der erste erstellt eine Pipeline mit einer Stufe, die zweite wird zwei Stufen umfassen.

  2. Zwei Iteratoren haben einen leichten Leistungsnachteil zu einem.