为什么Linq在使用ToList时失败?

考虑这个琐碎的例子:

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

除了这是行不通的。 我得到这个例外:

exceptiontypes:System.ArrayTypeMismatchException

消息:源数组types不能分配给目标数组types。

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

  1. ArrayTypeMismatchException – 我没有做任何与数组,我自己。 这似乎是一个内部的例外。
  2. Cast<sbyte>可以正常工作(如第一个例子),当使用ToArrayToList ,问题就出现了。

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

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

编辑

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

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

好吧,这真的取决于几个古怪的结合:

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

  • 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 / sbyte转换,而不是尝试使用数组实现的CopyTo