为什么数组像IEnumerable一样被忽略延迟执行?

Dar*_*ryl 12 c# linq arrays generics

我今天遇到了这个问题而且我不明白发生了什么:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

0
1
2
0
1
2
Zero
One
Two
0
1
2
Run Code Online (Sandbox Code Playgroud)

我知道Cast()将导致延迟执行,但它看起来像将它转换为IEnumerable导致延迟执行丢失,并且只有当实际的实现集合是一个数组时.

为什么Print方法中的值的枚举导致enum被转换intList<Foo>集合,而不是Foo[]

Jon*_*eet 14

这是因为在面对意外的CLR转换时,不幸的是,这种优化有点被打破.

在CLR级别,有一个从a 到的引用转换- 你实际上根本不需要转换每个对象.在C#级别上并非如此,但它处于CLR级别.Foo[]int[]

现在,Cast<>包含一个优化,说"如果我已经处理了正确类型的集合,我可以返回相同的引用" - 实际上是这样的:

if (source is IEnumerable<T>)
{
    return source;
}
Run Code Online (Sandbox Code Playgroud)

所以a.Cast<int>回归a,这是一个Foo[].当你传递它时PrintGeneric,这很好,因为那时Tforeach循环中有一个隐式转换.编译器知道类型IEnumerator<T>.CurrentT,因此相关的堆栈槽是类型的T.当将值视为一个int而不是一个时,每个类型参数JIT编译的代码将"做正确的事" Foo.

但是,当您将数组作为一个传递时IEnumerable,该Current属性IEnumerator只是类型object,因此每个值将被装箱并传递给Console.WriteLine(object)- 并且盒装对象将是类型Foo,而不是int.

这里有一些示例代码来展示这个的第一部分 - 我相信,其余部分更容易理解,一旦你已经过去了:

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>()));
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你试图在中间uint[]int[]顺便说一下,你会看到同样的事情.