There is a weird inconsistency with converting and unboxing values and arrays:
var us = new uint[] { 1, 2, 3 };
var i1 = (int)us[0]; // direct value to value: works
var i2 = (int)(object)us[0]; // unboxing value to value: compiles, throws at runtime
var is1 = (int[])u; // direct array to array: does not compile
var is2 = (int[])(object)u; // unboxing array to array: works!
is2.GetType().Name // UInt32[]
Why is array unboxing allowed, when a direct conversion is prohibited?
Why is array unboxing allowed, when a direct conversion is prohibited?
If you do (int[])(object)us, you are doing two conversions, both of which are allowed by C#:
uint[] to object (you are allowed to cast any reference type to object)object to int[] (you are allowed to cast from object to any reference type)Therefore (int[])(object)us compiles.
If you do (int[])us, you are doing a single conversion from int[] to uint[], which is not allowed by C#. According to the C# spec, you can only cast from a T[] to U[] if T and U are reference types, and that you can cast from T to U. int and uint satisfy neither of those, so (int[])us does not compile.
At runtime, the cast from uint[] to int[] is checked to see if it succeeds or fails. The CLR does allow this. See section I.8.7.1 of the CLR spec.
A signature type T is compatible-with a signature type U if and only if at least one of the following holds.
[...]
T is an array with rank r and element type V, and U is an array with the same rank r and element type W, and V is array-element-compatible-with W.
[...]
A signature type T is array-element-compatible-with a signature type U if and only if T has underlying type V and U has underlying type W and either:
- V is compatible-with W; or
- V and W have the same reduced type.
And the "reduced types" of int and uint are both int.
This is why (int[])(object)us succeeds at runtime.
Also note that casting us to object is not "boxing". Everything is a reference type here. You're just changing the type of the reference.