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

时间:2015-07-06 20:17:22

标签: c# casting clr

以下代码:

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

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

  

数组是uint []:是的   array [0]:FFFFFFFF

这似乎是我所特有的,因为它似乎打破了类型安全。执行以下操作也是编译时错误:

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

这种不一致来自何处?为什么CLR实现这样?

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

1 个答案:

答案 0 :(得分:2)

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

来自评论:

  

你知道为什么CLR允许这种转换吗?它是否有助于CLI合规性(即允许没有无符号类型的语言将数组视为有符号类型)

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

在CIL固有的有符号和无符号类型之间可以自由移动。

现在,协方差意味着我们可以分配一个等于或窄于其赋值的值;这就是它包括bivariance(指定更窄的东西)。 C#不考虑intuint bivariant;你必须明确地在它们之间施放,所以将它们包含在协变指派中是没有意义的。