为什么使用ToList时这个Linq转换失败? [英] Why does this Linq Cast Fail when using 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();
//这里没有看到
foreach(var sb in baz)
{
Console.WriteLine(sb);
}
除了这些不起作用。我遇到了以下异常:
异常类型:System.ArrayTypeMismatchException
源数组类型不能分配给目标数组类型。
我发现这个异常非常奇怪,因为
-
ArrayTypeMismatchException
- 我不对数组做任何事情,我自己。这似乎是一个内部异常。 -
Cast< sbyte>
工作正常(如第一个示例)ToArray
或ToList
问题本身。
我的目标是.NET v4 x86,但同样发生在3.5。
我不需要任何建议如何解决问题,我已经设法做到了。
EDIT :
b $ b即使weirder,添加一个无意义的select语句也会导致 ToList
正常工作:
var baz = bar.Select(x => x).ToList();
好吧,这真的取决于几个怪物的组合:
-
即使在C#中,您无法投射
byte []
直接到sbyte []
,CLR允许它:var foo = new byte [] {246,127};
//这在编译时产生一个警告,C#编译器优化
//到常量false
Console.WriteLine(foo is sbyte []);
object x = foo;
//使用对象傻瓜C#编译器真正咨询CLR ...其中
//允许转换,所以这打印True
Console.WriteLine(x is sbyte []);
-
Cast< T>()
优化,如果它认为它不需要做任何事情(通过是
检查像上面),它返回原始的引用 - 这是发生在这里。 p> -
ToList()
委派给List< T& c $ c>获取
IEnumerable< T>
-
c> ICollection< T> 使用
CopyTo
...和是什么失败。这是一个没有方法调用其他比CopyTo
的版本:object bytes = new byte [] {246,127};
//这将成功...
ICollection< sbyte> list =(ICollection< sbyte>)bytes;
sbyte [] array = new sbyte [2];
list.CopyTo(array,0);
现在如果使用选择
在任何时候,你最终不是一个 ICollection< T>
,所以它通过合法) byte /
sbyte
转换,而不是尝试使用 CopyTo
。
Consider this contrived, trivial example:
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);
}
With the magic of Two's Complement, -10 and 127 is printed to the console. So far so good. People with keen eyes will see that I am iterating over an enumerable and adding it to a list. That sounds like 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);
}
Except that does not work. I get this exception:
Exception type: System.ArrayTypeMismatchException
Message: Source array type cannot be assigned to destination array type.
I find this exception very peculiar because
ArrayTypeMismatchException
- I'm not doing anything with arrays, myself. This seems to be an internal exception.- The
Cast<sbyte>
works fine (as in the first example), it's when usingToArray
orToList
the problem presents itself.
I'm targeting .NET v4 x86, but the same occurs in 3.5.
I don't need any advice on how to resolve the problem, I've already managed to do that. What I do want to know is why is this behavior occurring in the first place?
EDIT:
Even weirder, adding a meaningless select statement causes the ToList
to work correctly:
var baz = bar.Select(x => x).ToList();
Okay, this really depends on a few oddities combined:
Even though in C# you can't cast a
byte[]
to ansbyte[]
directly, the CLR allows it: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>()
optimizes such that if it thinks it doesn't need to do anything (via anis
check like the above) it returns the original reference - so that's happening here.ToList()
delegates to the constructor ofList<T>
taking anIEnumerable<T>
That constructor is optimized for
ICollection<T>
to useCopyTo
... and that's what's failing. Here's a version which has no method calls other thanCopyTo
:object bytes = new byte[] { 246, 127 }; // This succeeds... ICollection<sbyte> list = (ICollection<sbyte>) bytes; sbyte[] array = new sbyte[2]; list.CopyTo(array, 0);
Now if you use a Select
at any point, you don't end up with an ICollection<T>
, so it goes through the legitimate (for the CLR) byte
/sbyte
conversion for each element, rather than trying to use the array implementation of CopyTo
.
这篇关于为什么使用ToList时这个Linq转换失败?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!