12

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

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被强制转换intList<Foo>集合,而不是Foo[]

4

1 回答 1

14

这是因为一个优化在面对意外的 CLR 转换时被稍微破坏了。

在 CLR 级别,有一个从 a到的引用转换——您实际上根本不需要强制转换每个对象。在 C# 级别不是这样,但在 CLR 级别是这样。Foo[]int[]

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

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

所以a.Cast<int>返回a,这是一个Foo[]. 当你将它传递给 时很好,因为在循环中PrintGeneric有一个隐式转换。编译器知道是的类型,所以相关的栈槽是类型。当将值视为 an而不是.TforeachIEnumerator<T>.CurrentTTintFoo

但是,当您将数组作为 传递时IEnumerableCurrent上的属性IEnumerator只是 type object,因此每个值都将被装箱并传递给Console.WriteLine(object)- 装箱的对象将是 type 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[]顺便说一句,您会看到同样的事情。

于 2013-11-04T16:28:57.517 回答