为什么某些值类型的数组之间的强制转换似乎会破坏类型安全?

Why do casts between arrays of some value types seemingly break type safety?

以下代码:

object array = new int[] {-1};
Console.WriteLine("array is uint[]: {0}", array is uint[]);
Console.WriteLine("array[0]: {0:X}", ((uint[])array)[0]);

打印以下内容,没有任何错误:

array is uint[]: True
array[0]: FFFFFFFF

这对我来说似乎很奇怪,因为它似乎破坏了类型安全。执行以下操作也是编译时错误:

int[] array = {-1};
uint[] test = (uint[])array;

这种不一致从何而来?为什么CLR是这样实现的?

请注意,我不认为这与数组协方差是一回事。在数组协变中,转换是允许的,因为存在隐式引用转换;这里不是这种情况,两种类型都是值类型,并且它们之间只有显式转换。
对于数组协变,运行时也会在某些情况下抛出异常(当赋值没有意义时)。在这种情况下,运行时不会抛出异常,即使将 test[0] 分配给 Int32.

范围之外的值也是如此

.NET 中的数组以一种相当破碎的方式协变。

C# 中的数组在它们在 .NET 中的协变方式的子集 中是协变的。还是坏了,但是不允许:

uint[] test = (uint[])new int[10]; // Compiler Error CS0030 C#
                                   // doesn't allow this covariance

但这是 C# 规则; .NET 允许它,它允许在类型为相同大小的整数(有符号或无符号)的数组或具有相同大小的基础类型的枚举之间进行赋值。

当然,.NET 和 C# 都允许您将任何数组转换为 object,并且从对象转换为任何数组类型都是合法的,但在运行时可能会失败。所以uint[] test = (uint[])(object)new int[10];是允许的,因为就像下面的步骤:

object temp = new int[10];  // normal enough asignmnt.
uint[] test = (uint[])temp; // C# doesn't allow assigning
                            // int[] to uint[] but temp is
                            // object so the compiler
                            // doesn't know that's what
                            // you are doing, and .NET
                            // does allow it.

来自评论:

Do you know why the CLR allows this conversion at all? Does it help with CLI compliance maybe (ie. allow languages without unsigned types to treat arrays as signed types)

考虑一下,在 CIL 中,有符号值和无符号值之间的堆栈值差异较小。如果您使用 clt 它将从堆栈中弹出两个值并将它们作为有符号值进行比较,无论它们是否有符号,而 clt.un 会将它们作为无符号值进行比较,无论它们是否无符号。同样

CIL 固有的相同大小的有符号和无符号类型之间可以自由移动。

现在,协方差意味着我们可以分配一个等于或小于分配给它的值;那就是它包括双方差(分配更窄的东西)。 C# 不考虑 intuint 双变;您必须在它们之间显式转换,因此将它们包含在协变赋值中没有意义。