为什么使用ToList时这个Linq转换失败? [英] Why does this Linq Cast Fail when using ToList?

查看:333
本文介绍了为什么使用ToList时这个Linq转换失败?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这个设计的例子:

  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



源数组类型不能分配给目标数组类型。


我发现这个异常非常奇怪,因为


  1. ArrayTypeMismatchException - 我不对数组做任何事情,我自己。这似乎是一个内部异常。

  2. 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

  1. ArrayTypeMismatchException - I'm not doing anything with arrays, myself. This seems to be an internal exception.
  2. The Cast<sbyte> works fine (as in the first example), it's when using ToArray or ToList 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 an sbyte[] 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 an is check like the above) it returns the original reference - so that's happening here.

  • ToList() delegates to the constructor of List<T> taking an IEnumerable<T>

  • That constructor is optimized for ICollection<T> to use CopyTo... and that's what's failing. Here's a version which has no method calls other than CopyTo:

    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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆