2017-04-21 20 views
-1

Ich muss wissen, welche dieser Ansatz schneller ist.Leistung beim Iterieren über IQueryable in C#

// Approach1 
var filteredList = MyList.Where(x => x.IsNull); 
foreach (var item in filteredList) 
{ 
    // Do someghing... 
} 

// Approach2 
foreach (var item in MyList.Where(x => x.IsNull)) 
{ 
    // Do someghing... 
} 

Hat der letzte die Where jedes Mal die foreach Iterierten ausführen?

+5

Nein, das wäre lächerlich. Welchen Sinn würde es machen, bei jeder Iteration einen neuen Iterator zu bekommen? –

+0

Durch Hinzufügen einer, wo Sie unterschiedliche Ausführung erhalten, weil Sie jetzt auf ein IEnumerable handeln, wird die Bedingung bei jeder Iteration überprüft, aber wenn Sie diese konditionierte Aufzählung mehrmals verwenden müssten, wäre es etwas optimaler zu drehen dass IEnumerable zurück in eine Liste –

+0

Sie tun genau das gleiche –

Antwort

6

Es gibt so gut wie keinen Unterschied zwischen diesen beiden.

In dieser Version

// Approach1 
var filteredList = MyList.Where(x => x.IsNull); 
foreach (var item in filteredList) 
{ 
    // Do someghing... 
} 

Sie sagen im Wesentlichen

  1. einen Iterator Get (IEnumerable<T>) von irgendwo
  2. auf eine Variable zu diesem Iterator eine Referenz zuweisen.
  3. Iterate über den Iterator

In dieser Version

// Approach2 
foreach (var item in MyList.Where(x => x.IsNull)) 
{ 
    // Do someghing... 
} 

Sie sagen im Wesentlichen

  1. einen Iterator Get (IEnumerable<T>) von irgendwo
  2. Iterate über den Iterator

So ist der Unterschied sehr gering - das einzige, was in approach1 hinzukommt, ist die Zuweisung einer Referenzvariablen zu einer anderen Referenzvariablen, was eine Frage des Verschiebens von ein paar Bytes im Speicher ist.

Darüber hinaus könnte der Compiler nur die Variable filteredList optimieren, in diesem Fall wird es genau die gleiche IL für beide Ansätze emittieren.

+5

... die wahrscheinlich vom Compiler optimiert werden. – GalacticCowboy

+0

* was ist eine Frage der Bewegung von vier Bytes im Speicher ... * unter Berücksichtigung 32-Bit-Maschine (OS) – Rahul

+0

Was ist genau das, was meine Antwort unten zeigt @John wird – loneshark99

0

Beide Schleifen sind identisch.

Der Code innerhalb der Where() führt für jedes Element auf MyList - Sie haben es als Kriterium festgelegt, um zu entscheiden, ob ein Element in der Liste im Ergebnis enthalten sein soll.

So

x => x.IsNull 

führt für jedes Element in MyList, aber der Körper Ihrer foreach-Schleife:

// Do something 

wird nur für diejenigen Elemente ausführen, die die Kriterien erfüllen, und es wird Artikel zugewiesen. Das heißt, für jedes Mal, wenn Ihre foreach-Schleife ausgeführt wird, hat das Objekt, das es "sieht", item.IsNull true.

2

Überprüfen Sie die Dokumentation auf Enumerable.Where() wie es zitiert:

Die Abfrage durch dieses Verfahren nicht dargestellt ist, bis das Objekt ausgeführt aufgezählt wird, entweder durch ihre GetEnumerator-Methode direkt oder Aufruf von foreach in Visual C#

So im wesentlichen, gibt es keinen Unterschied zwischen den beiden Ansatz, den Sie geschrieben haben

3

Es lohnt re ading up on how foreach works (nicht nur für diese Frage, sondern als eine Angelegenheit von allgemeinem Interesse): Es erhält einen Enumerator (überraschend breit definiert) aus dem Ergebnis des Ausdrucks rechts von in und ruft dann bool MoveNext() auf diesem Enumerator, bis MoveNext() zurückgibt falsch.

Es würde keinen Sinn ergeben, foreach wiederholt den Ausdruck auszuwerten, aus dem es den Enumerator bezieht. In der Tat, .NET ist sehr bemüht, Sie daran zu hindern, eine Sequenz zu ändern, während sie aufgezählt wird - mit anderen Worten, um sicherzustellen, dass sie genau das gleiche aufzählt, mit dem sie begonnen hat. Versuchen Sie, ein Element an einen List<T> Hinzufügen, während Sie in einer foreach drauf sind:

var list = Enumerable.Range(0, 10).ToList(); 

foreach (var x in list) 
{ 
    list.Add(x); 
} 

„Collection modifiziert wurde; Enumerationsvorgang nicht ausgeführt werden kann.“

Kein Verkauf.

Und wenn es neue Enumeratoren bekommen würde, müsste es Elemente in ihnen überspringen. Erste Iteration, erstes Element holen. Zweitens: Nimm einen neuen Enumerator, überspringe ihn, nimm den nächsten. Drittens: Besorgen Sie sich einen neuen Enumerator, überspringen Sie zwei, springen Sie von einer Brücke.

Das ist Wahnsinn. Es ist keine Möglichkeit für Profis, Code oder sogar halbnüchterne Amateure zu schreiben.

Also, es ist nur mit dem Ergebnis Ihres Ausdrucks. Es spielt keine Rolle, ob der Ausdruck zufällig zwischen den Parens liegt oder ob Sie das Ergebnis einer lokalen Variablen zugewiesen haben.

Beide Versionen sind identisch.

Es gibt auch die Möglichkeit von potenziell offenen Enumerationen - etwas in dort könnte auf einem parallelen Thread sitzen, in der Warteschlange von Zeug aus einem Gerätetreiber oder etwas, und die Enumeration nicht unbedingt enden. Aber Sie würden immer noch den gleichen Enumerator aus beiden Versionen Ihres Codes erhalten.

+1

mochte das Beispiel der * Sammlung geändert ... * – Rahul

0

Sie sind beide identisch, das ist die Schönheit der Iteratoren. Sie können auch die IL überprüfen, zu der es kompiliert wird, was in LINQPAD gleich ist.

IL_0033: ldloc.0  // list 
IL_0034: ldsfld  UserQuery+<>c.<>9__0_0 
IL_0039: dup   
IL_003A: brtrue.s IL_0053 
IL_003C: pop   
IL_003D: ldsfld  UserQuery+<>c.<>9 
IL_0042: ldftn  UserQuery+<>c.<Main>b__0_0 
IL_0048: newobj  System.Func<UserQuery+Test,System.Boolean>..ctor 
IL_004D: dup   
IL_004E: stsfld  UserQuery+<>c.<>9__0_0 
IL_0053: call  System.Linq.Enumerable.Where<Test> 
IL_0058: stloc.3  // filteredList 
IL_0059: nop   
IL_005A: ldloc.3  // filteredList 
IL_005B: callvirt System.Collections.Generic.IEnumerable<UserQuery+Test>.GetEnumerator 
IL_0060: stloc.s  04 
IL_0062: br.s  IL_0077 
IL_0064: ldloc.s  04 
IL_0066: callvirt System.Collections.Generic.IEnumerator<UserQuery+Test>.get_Current 
IL_006B: stloc.s  05 // item 
IL_006D: nop   
IL_006E: ldloc.s  05 // item 
IL_0070: call  LINQPad.Extensions.Dump<Test> 
IL_0075: pop   
IL_0076: nop   
IL_0077: ldloc.s  04 
IL_0079: callvirt System.Collections.IEnumerator.MoveNext 
IL_007E: brtrue.s IL_0064 
IL_0080: leave.s  IL_008F 
IL_0082: ldloc.s  04 
IL_0084: brfalse.s IL_008E 
IL_0086: ldloc.s  04 
IL_0088: callvirt System.IDisposable.Dispose 
IL_008D: nop   
IL_008E: endfinally 
IL_008F: nop   


IL_0090: ldloc.0  // list 
IL_0091: ldsfld  UserQuery+<>c.<>9__0_1 
IL_0096: dup   
IL_0097: brtrue.s IL_00B0 
IL_0099: pop   
IL_009A: ldsfld  UserQuery+<>c.<>9 
IL_009F: ldftn  UserQuery+<>c.<Main>b__0_1 
IL_00A5: newobj  System.Func<UserQuery+Test,System.Boolean>..ctor 
IL_00AA: dup   
IL_00AB: stsfld  UserQuery+<>c.<>9__0_1 
IL_00B0: call  System.Linq.Enumerable.Where<Test> 
IL_00B5: callvirt System.Collections.Generic.IEnumerable<UserQuery+Test>.GetEnumerator 
IL_00BA: stloc.s  06 
IL_00BC: br.s  IL_00D1 
IL_00BE: ldloc.s  06 
IL_00C0: callvirt System.Collections.Generic.IEnumerator<UserQuery+Test>.get_Current 
IL_00C5: stloc.s  07 // item 
IL_00C7: nop   
IL_00C8: ldloc.s  07 // item 
IL_00CA: call  LINQPad.Extensions.Dump<Test> 


void Main() 
{ 
    List<Test> list = new List<Test>(); 
    var x1 = new Test() { IsNull = false }; 
    var x2 = new Test() { IsNull = true }; 
    list.Add(x1); 
    list.Add(x2); 
    // Approach1 
    var filteredList = list.Where(x => x.IsNull == true); 
    foreach (var item in filteredList) 
    { 
     item.Dump(); 
    } 
    // Approach2 
    foreach (var item in list.Where(x => x.IsNull == true)) 
    { 
     item.Dump(); 
    } 
}