我今天遇到了这个问题而且我不明白发生了什么:
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);
}
}
输出:
0
1
2
0
1
2
Zero
One
Two
0
1
2
我知道Cast()将导致延迟执行,但它看起来像将它转换为IEnumerable导致延迟执行丢失,并且只有当实际的实现集合是一个数组时。
为什么枚举值中的值 Print
方法导致 enum
被投射到 int
为了 List<Foo>
收藏,但不是 Foo[]
?
这是因为在面对意外的CLR转换时,不幸的是有一点优化。
在CLR级别,有一个 参考 转换自 Foo[]
至 int[]
- 实际上你根本不需要投射每个对象。在C#级别上并非如此,但它处于CLR级别。
现在, Cast<>
包含一个优化,说“如果我已经在处理正确类型的集合,我可以返回相同的引用” - 有效地像这样:
if (source is IEnumerable<T>)
{
return source;
}
所以 a.Cast<int>
回报 a
,这是一个 Foo[]
。传递给你时没关系 PrintGeneric
,因为那时有一个隐含的转换 T
在里面 foreach
循环。编译器知道的类型 IEnumerator<T>.Current
是 T
,所以相关的堆栈槽是类型 T
。当将值视为一个时,每类型参数JIT编译的代码将“做正确的事” int
而不是作为一个 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>()));
}
}
如果你试图介入,你会看到同样的事情 uint[]
和 int[]
顺便一提。