使用ToList时为什么Linq Cast会失败?

时间:2012-06-14 18:48:06

标签: c# linq casting

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

    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);
    }

借助Two's Complement的魔力,-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<sbyte>工作正常(如第一个示例中所示),当使用ToArrayToList问题出现时。
  3. 我的目标是.NET v4 x86,但3.5版也是如此。

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

    修改

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

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

1 个答案:

答案 0 :(得分:28)

好的,这实际上取决于几个奇怪的组合:

  • 即使在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<T>()优化,如果它认为它不需要做任何事情(通过上面的is检查)它会返回原始引用 - 所以这就发生了。< / p>

  • ToList()委托给List<T> IEnumerable<T>

  • 的构造函数
  • 该构造函数针对ICollection<T>进行了优化,以使用CopyTo ...和 失败的内容。这是一个版本没有方法调用其他而不是CopyTo

    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<T>,因此它会通过合法(针对CLR)byte / {{ 1}}转换每个元素,而不是尝试使用sbyte的数组实现。