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