【问题标题】:Why does this Linq Cast Fail when using ToList?为什么使用 ToList 时此 Linq Cast 失败?
【发布时间】:2012-06-14 18:48:05
【问题描述】:

考虑这个人为的、琐碎的例子:

    var foo = new byte[] {246, 127};
    var bar = foo.Cast<sbyte>();
    var baz = new List<sbyte>();
    foreach (var sb in bar)
    {
        baz.Add(sb);
    }
    foreach (var sb in baz)
    {
        Console.WriteLine(sb);
    }

借助二进制补码的魔力,-10 和 127 会打印到控制台。到现在为止还挺好。目光敏锐的人会看到我正在迭代一个可枚举并将其添加到列表中。这听起来像ToList

    var foo = new byte[] {246, 127};
    var bar = foo.Cast<sbyte>();
    var baz = bar.ToList();
    //Nothing to see here
    foreach (var sb in baz)
    {
        Console.WriteLine(sb);
    }

除非那不起作用。我得到了这个例外:

异常类型:System.ArrayTypeMismatchException

消息:源数组类型无法分配给目标数组类型。

我觉得这个异常很奇怪,因为

  1. ArrayTypeMismatchException - 我自己没有对数组做任何事情。这似乎是一个内部异常。
  2. Cast&lt;sbyte&gt; 工作正常(如第一个示例中所示),当使用 ToArrayToList 时,问题就出现了。

我的目标是 .NET v4 x86,但在 3.5 中也是如此。

我不需要任何关于如何解决问题的建议,我已经设法做到了。我想知道的是为什么会发生这种行为?

编辑

更奇怪的是,添加无意义的 select 语句会导致 ToList 正常工作:

var baz = bar.Select(x => x).ToList();

【问题讨论】:

  • 使用Select 会产生{ -10, 127 }。这里有一个选角问题。肯定是有趣的错误消息。
  • @Lieven 是的,我收集了这么多,为什么Select(x =&gt; x)ToList 之前更正它?这是一个毫无意义的投射,因为同样的东西被投射回来。
  • 我有一个解释......只是需要一点时间才能写出来。好问题。
  • @vcsjones:因为这样很可能不再使用数组助手 - 使用 Select 您将投影到 IEnumerable
  • +1 好问题。我喜欢涉及有趣边缘案例的问题。

标签: c# linq casting


【解决方案1】:

好的,这真的取决于一些奇怪的组合:

  • 即使在 C# 中您不能将 byte[] 直接转换为 sbyte[],但 CLR 允许:

    var foo = new byte[] {246, 127};
    // This produces a warning at compile-time, and the C# compiler "optimizes"
    // to the constant "false"
    Console.WriteLine(foo is sbyte[]);
    
    object x = foo;
    // Using object fools the C# compiler into really consulting the CLR... which
    // allows the conversion, so this prints True
    Console.WriteLine(x is sbyte[]);
    
  • Cast&lt;T&gt;() 进行了优化,如果它认为不需要做任何事情(通过is 进行上述检查),它会返回原始引用 - 所以这里发生了。

  • ToList()List&lt;T&gt; 的构造函数委托给IEnumerable&lt;T&gt;

  • 该构造函数针对 ICollection&lt;T&gt; 进行了优化,以使用 CopyTo...这就是失败的原因。这是一个除了CopyTo之外没有方法调用other的版本:

    object bytes = new byte[] { 246, 127 };
    
    // This succeeds...
    ICollection<sbyte> list = (ICollection<sbyte>) bytes;
    
    sbyte[] array = new sbyte[2];
    
    list.CopyTo(array, 0);
    

现在,如果您在任何时候使用Select,您最终都不会得到ICollection&lt;T&gt;,因此它会针对每个元素进行合法的(对于CLR)byte/sbyte 转换,而不是尝试使用CopyTo的数组实现。

【讨论】:

  • 是的,我注意到 bar 在运行时的值类型是 byte[],而不是 System.Linq.Enumerable.CastIterator。感谢您的解释!
  • 看完问答后,是不是意味着 .ToList 需要在类型安全的转换中使用?
  • @Turbot:我不明白你的评论。你能改写一下吗?
  • 嗯,现在我明白了。 Cast 在第一个或第二个示例中从未执行任何操作,而是通过将其添加到列表和 CLR 处理转换来处理转换。
  • @JonSkeet 哎呀,这与类型安全转换无关。这只是与 C# 编译器和 CLR 处理转换不同的 Treatment
猜你喜欢
  • 1970-01-01
  • 2018-05-15
  • 1970-01-01
  • 2014-11-23
  • 2014-01-06
  • 1970-01-01
  • 2016-02-27
  • 2012-07-18
  • 1970-01-01
相关资源
最近更新 更多