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.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();
ToList() makes a shallow copy. The references are copied, but the new references still point to the same instances as the original references point to.
ToList() simply creates a new List that contains a private array that is added to during each iteration. The very first iteration will create a new array with defaultCapacity = 4 , just like in ToArray() . Inside the Capacity setter, a new array which is doubled in size, is created when the initial array is full.
The ToList<TSource>(IEnumerable<TSource>) method forces immediate query evaluation and returns a List<T> that contains the query results. You can append this method to your query in order to obtain a cached copy of the query results.
LINQ ToList() Method In LINQ, the ToList operator takes the element from the given source, and it returns a new List. So, in this case, input would be converted to type List.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With