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