【问题标题】:Why Does an Array Cast as IEnumerable Ignore Deferred Execution?为什么数组转换为 IEnumerable 忽略延迟执行?
【发布时间】:2013-11-15 07:59:48
【问题描述】:

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

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&lt;Foo&gt; 集合,而不是Foo[]

【问题讨论】:

  • PrintGeneric 中 Console.WriteLine(value.GetType().Name) 的输出是什么?
  • @SimSimY Int32。当 List&lt;Foo&gt;.Cast&lt;int&gt;() 传入时,Print 是 Int32,但是当 Foo[].Cast&lt;int&gt;() 传入时,Foo 是 Int32。
  • 这与延迟执行有什么关系?
  • @Servy 我错误地假设延迟执行被忽略了。
  • @Daryl 什么操作被推迟了,以后而不是更早地执行它是如何相关的?这些是您在延迟执行相关问题中应该问自己的问题。

标签: c# arrays linq generics


【解决方案1】:

这是因为一个优化在面对意外的 CLR 转换时略微中断。

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

现在,Cast&lt;&gt; 包含一个优化,表示“如果我已经在处理正确类型的集合,我可以返回相同的引用” - 实际上就像这样:

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

所以a.Cast&lt;int&gt; 返回a,即Foo[]。当您将它传递给PrintGeneric 时,这很好,因为在foreach 循环中有一个到T 的隐式转换。编译器知道IEnumerator&lt;T&gt;.Current的类型是T,所以相关的栈槽是T的类型。当将值视为 int 而不是 Foo 时,按类型参数 JIT 编译的代码将“做正确的事”。

但是,当您将数组作为IEnumerable 传递时,IEnumerator 上的Current 属性只是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[] 之间切换,您会看到同样的情况。

【讨论】:

  • 这实际上不会那么糟糕,如果它只是更一致的话。在这种情况下,Listarray 应该满足is IEnumerable&lt;T&gt; 条件,但只有一个表现出优化。
  • @JoelCoehoorn:它是 CLR 中 array 等价的特殊部分,而不是一般的IEnumerable&lt;T&gt; 等价。
  • 在我的实际生产代码中,我有一个接受 IEnumerable 的方法,其中 T 是一个枚举。然后,在将 IEnumerable 传递给另一个方法时,我在其上调用 Cast&lt;int&gt;。有没有办法调用ToList()就可以了?
  • PrintGeneric 怎么打电话给Console.WriteLine(int)?由于PrintGeneric 中的T 没有类型限制,所以WriteLine 调用不会编译为Console.WriteLine(object)
  • @Heinzi:对不起,你是对的——当然会。但是隐式转换为T 将转换单个值。将编辑我的答案。
猜你喜欢
  • 1970-01-01
  • 2017-01-15
  • 2020-07-02
  • 1970-01-01
  • 2018-06-24
  • 1970-01-01
  • 2015-06-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多