为什么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。
我觉得这个例外非常奇特,因为
-
ArrayTypeMismatchException
– 我没有做任何与数组,我自己。 这似乎是一个内部的例外。 -
Cast<sbyte>
可以正常工作(如第一个例子),当使用ToArray
或ToList
,问题就出现了。
我的目标是.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
。