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

vcs*_*nes 35 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);
    }
Run Code Online (Sandbox Code Playgroud)

借助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);
    }
Run Code Online (Sandbox Code Playgroud)

除此之外不起作用.我得到这个例外:

异常类型:System.ArrayTypeMismatchException

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

我觉得这个例外非常奇怪,因为

  1. ArrayTypeMismatchException - 我自己也没有对阵列做任何事情.这似乎是一个内部例外.
  2. Cast<sbyte>罚款(如在第一个例子)的作品,它使用时的ToArrayToList问题提出了自己.

我的目标是.NET v4 x86,但同样的情况发生在3.5.

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

编辑:

甚至更奇怪,添加一个无意义的select语句会导致它ToList正常工作:

var baz = bar.Select(x => x).ToList();
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 28

好吧,这真的取决于几个奇怪的组合:

  • 即使在C#中你不能直接byte[]转换为a 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[]);
    
    Run Code Online (Sandbox Code Playgroud)
  • Cast<T>()优化,如果它认为它不需要做任何事情(通过is上面的检查)它返回原始引用 - 所以这发生在这里.

  • 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);
    
    Run Code Online (Sandbox Code Playgroud)

现在如果你Select在任何时候使用a ,你最终都不会得到一个ICollection<T>,所以它通过合法的(对于CLR)byte/ sbyte转换每个元素,而不是试图使用数组实现CopyTo.

  • 是的,我注意到运行时bar的值类型是byte [],而不是System.Linq.Enumerable.CastIterator <sbyte>.谢谢你解释! (2认同)