2014-11-12 17 views
9

erwartete ich die Implementierung von Enumerable.Empty() dabei, um nur:Warum gibt Enumerable.Empty() ein leeres Array zurück?

public static IEnumerable<TResult> Empty<TResult>() 
{ 
    yield break; 
} 

Aber die Umsetzung ist in etwa so:

public static IEnumerable<TResult> Empty<TResult>() 
{ 
    return EmptyEnumerable<TResult>.Instance; 
} 

internal class EmptyEnumerable<TElement> 
{ 
    private static volatile TElement[] instance; 

    public static IEnumerable<TElement> Instance 
    { 
     get 
     { 
      if (EmptyEnumerable<TElement>.instance == null) 
       EmptyEnumerable<TElement>.instance = new TElement[0]; 
      return (IEnumerable<TElement>)EmptyEnumerable<TElement>.instance; 
     } 
    } 
} 

Warum funktioniert die Implementierung als nur eine Zeile komplexer von Code? Gibt es einen Vorteil, ein zwischengespeichertes Array zurückzugeben und nicht (Ausbeute) keine Elemente zurückzugeben?

Hinweis: Ich werde mich nie auf die Implementierungsdetails einer Methode verlassen, aber ich bin nur neugierig.

+10

'Ausbeute break' eine Zeile in C# ist, aber es ist viel komplexer, wenn Sie schauen am generierten IL-Code. – Dirk

Antwort

6

Kompilieren (mit LINQPad mit Optimierungen aktiviert)

public static IEnumerable<TResult> MyEmpty<TResult>() 
{ 
    yield break; 
} 

führt zu einem ziemlich viel Code.

Es wird eine Zustandsmaschine erstellt, die die IEnumerable Schnittstelle implementiert. Jedes Mal, wenn Sie MyEmpty aufrufen, wird eine neue Instanz dieser Klasse erstellt. Die gleiche Instanz eines leeren Arrays zurückzugeben ist ziemlich billig.

Der IL-Code für EmptyEnumerable ist:

EmptyEnumerable`1.get_Instance: 
IL_0000: volatile. 
IL_0002: ldsfld  16 00 00 0A 
IL_0007: brtrue.s IL_0016 
IL_0009: ldc.i4.0  
IL_000A: newarr  04 00 00 1B 
IL_000F: volatile. 
IL_0011: stsfld  16 00 00 0A 
IL_0016: volatile. 
IL_0018: ldsfld  16 00 00 0A 
IL_001D: castclass 01 00 00 1B 
IL_0022: ret 

Und für die MyEmpty Methode ist es:

MyEmpty: 
IL_0000: ldc.i4.s FE 
IL_0002: newobj  15 00 00 0A 
IL_0007: stloc.0  
IL_0008: ldloc.0  
IL_0009: ret   

<MyEmpty>d__0`1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator: 
IL_0000: call  System.Environment.get_CurrentManagedThreadId 
IL_0005: ldarg.0  
IL_0006: ldfld  0E 00 00 0A 
IL_000B: bne.un.s IL_0022 
IL_000D: ldarg.0  
IL_000E: ldfld  0F 00 00 0A 
IL_0013: ldc.i4.s FE 
IL_0015: bne.un.s IL_0022 
IL_0017: ldarg.0  
IL_0018: ldc.i4.0  
IL_0019: stfld  0F 00 00 0A 
IL_001E: ldarg.0  
IL_001F: stloc.0  
IL_0020: br.s  IL_0029 
IL_0022: ldc.i4.0  
IL_0023: newobj  10 00 00 0A 
IL_0028: stloc.0  
IL_0029: ldloc.0  
IL_002A: ret   

<MyEmpty>d__0`1.System.Collections.IEnumerable.GetEnumerator: 
IL_0000: ldarg.0  
IL_0001: call  11 00 00 0A 
IL_0006: ret   

<MyEmpty>d__0`1.MoveNext: 
IL_0000: ldarg.0  
IL_0001: ldfld  0F 00 00 0A 
IL_0006: stloc.0  // CS$0$0000 
IL_0007: ldloc.0  // CS$0$0000 
IL_0008: ldc.i4.0  
IL_0009: bne.un.s IL_0012 
IL_000B: ldarg.0  
IL_000C: ldc.i4.m1 
IL_000D: stfld  0F 00 00 0A 
IL_0012: ldc.i4.0  
IL_0013: ret   

<MyEmpty>d__0`1.System.Collections.Generic.IEnumerator<TResult>.get_Current: 
IL_0000: ldarg.0  
IL_0001: ldfld  12 00 00 0A 
IL_0006: ret   

<MyEmpty>d__0`1.System.Collections.IEnumerator.Reset: 
IL_0000: newobj  System.NotSupportedException..ctor 
IL_0005: throw  

<MyEmpty>d__0`1.System.IDisposable.Dispose: 
IL_0000: ret   

<MyEmpty>d__0`1.System.Collections.IEnumerator.get_Current: 
IL_0000: ldarg.0  
IL_0001: ldfld  12 00 00 0A 
IL_0006: box   04 00 00 1B 
IL_000B: ret   

<MyEmpty>d__0`1..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: ldarg.0  
IL_0007: ldarg.1  
IL_0008: stfld  0F 00 00 0A 
IL_000D: ldarg.0  
IL_000E: call  System.Environment.get_CurrentManagedThreadId 
IL_0013: stfld  0E 00 00 0A 
IL_0018: ret   
+0

Wie vergleicht das, wenn Sie auch den IL-Code hinzufügen, der für das Caching benötigt wird? –

+0

@AlexSiepman Ziemlich geringfügig, ich habe die Antwort damit aktualisiert. – Dirk

+0

@Dirk Wichtiger Hinweis dazu - obwohl es mehr IL erzeugt, ist es nicht, warum es nicht so gut funktioniert wie ein leeres Array. Der Hauptleistungsunterschied ist die Anzahl der Objekte, die erstellt werden. Es geht nicht darum, wie viel Code notwendig generiert wird. –

1

Es ist sinnvoll, dies zu tun, weil Sie in diesem Fall ein Array für alle leeren Instanzen des gleichen Typs hätten, die weniger Speicher benötigen. Aus diesem Grund ist die einzelne Array-Instanz statisch.

Da ein Array ohne Elemente nicht geändert werden kann, kann es durch keinen Code verschmutzt werden.

+0

Arrays sind unveränderlich? Seit wann? –

+2

Speziell ein Array für alle leeren Instanzen eines bestimmten Typs. – juharr

+4

Da es sich um ein * leeres * Array handelt, müssen Sie sich keine Gedanken darüber machen, dass es schmutzig wird, da keine Elemente mutiert werden müssen. Arrays sind nicht unveränderbar, aber ihre Länge ist so, so dass ein Array aus null Elementen effektiv unveränderbar ist. –

Verwandte Themen