I get an exception System.ArrayTypeMismatchException: Source array type cannot be assigned to destination array type for this code snippet:
var uints = GetArray();
if (uints is int[])
{
var list = ((int[])uints).ToList(); // fails when call ToList()
}
private Array GetArray()
{
var result = new uint[] { uint.MaxValue, 2, 3, 4, 5 };
return result;
}
Then I resort to Jon's answer in Why does "int[] is uint[] == true" in C#, it told me that because of GetArray() returns an Array, the conversion was postponed in runtime, and CLR allows this kind of int[] to uint[](vice versa) cast. And it actually works fine if I check values after conversion:
foreach (var i in ((int[])units))
{
System.Console.WriteLine(i.GetType());
System.Console.WriteLine(i);
}
I would get:
System.Int32
-1
System.Int32
2
//skipped...
However I kind of get confused why it would fail when calling ToList(), as this code below will work fine?
internal class Animal
{
}
internal class Cat : Animal
{
}
var cats = new Cat[] { new Cat(), new Cat() };
List<Animal> animals = ((Animal[])cats).ToList(); //no exception
You are trying to cast the array to int[] when it is a uint[]. That by itself is fine, since both int[] and uint[] are of type Array, and casting an Array object of type uint[] as an int[] (or vice versa) is allowed (for some reason).
The problem comes when you try to then convert it to a List. The method is trying to create a List of type int because that's the type you specified, but that's not what the type of the array actually is.
What is happening here? Let's start with foreach loop, C# compiler optimizes them heavily especially when you use them with arrays - they are basically changes to normal for(int i = 0; i < length; i++) { } - so the example with
foreach (var i in ((int[])units))
{
System.Console.WriteLine(i.GetType());
System.Console.WriteLine(i);
}
cannot be trusted, BTW foreach can also perform cast on element type it is better to try generic Array.GetValue method:
int[] x = ((int[])uints);
Console.WriteLine(x.GetValue(0).GetType()); // System.UInt32
Console.WriteLine(x[0].GetType()); // System.Int32
So even access x[0] can return already casted value but Array.GetValue returned what was already there an uint.
Let's do another experiment:
Console.WriteLine(x.GetType()); // System.UInt32[]
Console.WriteLine(uints.GetType()); // System.UInt32[]
Console.WriteLine(Object.ReferenceEquals(x, uints)); // True
This assures us that cast in var x = (int[])uints is a NOP - no operation, it does nothing at all. Especially 3th line show us the we get exactly the same instance.
Now inside List constructor we have lines
_items = new T[count];
c.CopyTo(_items, 0);
that actually throw Array mismatch exception.
But why this exception wasn't thrown earlier e.g. when GetEnumerator() is called I don't know myself, I expected exception to be thrown on lines
x.GetEnumerator()
because types IEnumerable<int> and IEnumerable<uint> are not compatible but none was - maybe because .NET returns here System.Array+SZArrayEnumerator that is the same for every value type array.
EDIT: (After example with Cat)
Array covariance in C# assures us that any array of reference types can be assigned to object[] and that array of type Subclass[] can be assigned to BaseClass[]. The case with value types is different since they can have different sizes and/or conversion behaviours (uint vs int).
ToList uses internally Array.Copy call, when we look at Array.Copy implementation in CRL: https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/classlibnative/bcltype/arraynative.cpp#L328 we see that array can be copied only if value types have compatible singess which is checked by another util function https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/invokeutil.h#L196
The other question is why it was implemented this way...
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