2013-11-04 14 views
11

ich auf diese Frage lief und ich bin nicht zu verstehen, was los ist:Warum wird ein Array als IEnumerable ignoriert, wenn die verzögerte Ausführung ignoriert wird? heute

enum Foo 
{ 
    Zero, 
    One, 
    Two 
} 

void Main() 
{ 
    IEnumerable<Foo> a = new Foo[]{ Foo.Zero, Foo.One, Foo.Two}; 
    IEnumerable<Foo> b = a.ToList(); 

    PrintGeneric(a.Cast<int>()); 
    PrintGeneric(b.Cast<int>()); 

    Print(a.Cast<int>()); 
    Print(b.Cast<int>()); 
} 

public static void PrintGeneric<T>(IEnumerable<T> values){ 
    foreach(T value in values){ 
     Console.WriteLine(value); 
    } 
} 

public static void Print(IEnumerable values){ 
    foreach(object value in values){ 
     Console.WriteLine(value); 
    } 
} 

Ausgang:

0 
1 
2 
0 
1 
2 
Zero 
One 
Two 
0 
1 
2 

Ich weiß Cast() passiver Ausführung führen wird, aber es sieht wie es in IEnumerable umgewandelt wird, führt dazu, dass die verzögerte Ausführung verloren geht, und nur dann, wenn die tatsächliche Implementierungssammlung ein Array ist.

Warum ist die Aufzählung der Werte in dem Print Methode Ergebnis im enum zu einem int für die List<Foo> Sammlung geworfen werden, aber nicht die Foo[]?

+0

Was ist die Ausgabe von Console.WriteLine (value.GetType(). Name) in PrintGeneric? – SimSimY

+0

@SimSimY 'Int32'. Und es ist Int32 für den 'Print' wenn die' List .Cast () 'übergeben wird, aber' Foo' wenn der 'Foo []. Cast ()' vorbei ist. – Daryl

+4

Was muss das tun? mit verzögerter Ausführung? – Servy

Antwort

14

Es ist wegen einer Optimierung, die leider angesichts unerwarteter CLR-Konvertierungen leicht gebrochen ist.

Auf der CLR Ebene gibt es eine Referenz Umwandlung von einem Foo[] zu int[] - Sie brauchen eigentlich gar nicht jedes Objekt zu werfen. Das ist nicht auf C# -Niveau, aber auf CLR-Ebene.

Nun enthält Cast<> eine Optimierung zu sagen, „wenn ich bereits mit einer Sammlung von der richtigen Art zu tun, ich kann die gleiche Referenz zurückgeben“ - effektiv wie folgen aus:

if (source is IEnumerable<T>) 
{ 
    return source; 
} 

So a.Cast<int> gibt a zurück, das ist ein Foo[]. Das ist in Ordnung, wenn Sie es an PrintGeneric übergeben, denn dann gibt es eine implizite Umwandlung zu T in der foreach Schleife. Der Compiler weiß, dass der Typ IEnumerator<T>.CurrentT ist, der entsprechende Stack-Slot ist also vom Typ T. Der JIT-kompilierte Code pro Typ-Argument wird "das Richtige tun", wenn der Wert als int und nicht als Foo behandelt wird.

Wenn Sie jedoch das Array als IEnumerable geben, die Current Eigenschaft auf den IEnumerator ist nur vom Typ object, so wird jeder Wert verpackt und an Console.WriteLine(object) weitergegeben werden - und das Box-Objekt wird int vom Typ Foo, nicht .

Hier ist ein Beispielcode, den ersten Teil dieses zeigen - der Rest ist ein wenig einfacher zu verstehen, glaube ich, wenn man einmal, dass haben:

using System; 
using System.Linq; 

enum Foo { } 

class Test 
{ 
    static void Main() 
    { 
     Foo[] x = new Foo[10]; 
     // False because the C# compiler is cocky, and "optimizes" it out 
     Console.WriteLine(x is int[]); 

     // True because when we put a blindfold in front of the compiler, 
     // the evaluation is left to the CLR 
     Console.WriteLine(((object) x) is int[]); 

     // Foo[] and True because Cast returns the same reference back 
     Console.WriteLine(x.Cast<int>().GetType()); 
     Console.WriteLine(ReferenceEquals(x, x.Cast<int>())); 
    } 
} 

Sie werden das Gleiche sehen wenn Sie versuchen, zwischen uint[] und int[] übrigens zu gehen.

+1

Das wäre eigentlich nicht so schlimm, wenn es nur konsequenter wäre. In diesem Fall sollten die "Liste" und das "Array" die Bedingung "is IEnumerable " erfüllen, aber nur eine zeigt die Optimierung. –

+0

@JoelCoehoorn: Es ist ein spezieller Teil von * Array * Äquivalenz in der CLR, nicht allgemein 'IEnumerable ' Äquivalenz. –

+0

In meinem tatsächlichen Produktionscode habe ich eine Methode, die IEnumerable akzeptiert, wobei T eine Enum ist. Ich rufe dann 'Cast ' auf dem IEnumerable bei der Übergabe an eine andere Methode. Ist die Lösung, 'ToList()' darauf zu nennen? – Daryl