Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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();
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
299 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...