为什么此 Linq 强制转换在使用 ToList 时失败
本文关键字:ToList 失败 转换 Linq 为什么 | 更新日期: 2023-09-27 18:37:18
考虑这个人为的、微不足道的例子:
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 T补码的魔力,-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
消息:无法将源数组类型分配给目标数组类型。
我觉得这个例外非常奇特,因为
-
ArrayTypeMismatchException
- 我自己没有对数组做任何事情。这似乎是一个内部例外。 Cast<sbyte>
工作正常(如第一个示例所示),当使用ToArray
或ToList
时,问题就会出现。
我的目标是 .NET v4 x86,但在 3.5 中也会发生同样的情况。
我不需要任何关于如何解决问题的建议,我已经设法做到了。我想知道的是,为什么首先会发生这种行为?
编辑:
更奇怪的是,添加一个无意义的选择语句会导致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>()
进行优化,如果它认为它不需要做任何事情(通过像上面这样的is
检查),它会返回原始引用 - 所以这里正在发生。 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);
ToList()
委托给现在,如果您在任何时候使用Select
,您最终都不会得到ICollection<T>
,因此它会为每个元素进行合法(对于CLR)byte
/sbyte
转换,而不是尝试使用CopyTo
的数组实现。