为啥将数组拆箱为不正确的类型不会在 C# 中引发异常? [复制]

Posted

技术标签:

【中文标题】为啥将数组拆箱为不正确的类型不会在 C# 中引发异常? [复制]【英文标题】:Why does unboxing array to an incorrect type not throw an exception in C#? [duplicate]为什么将数组拆箱为不正确的类型不会在 C# 中引发异常? [复制] 【发布时间】:2021-11-24 22:03:55 【问题描述】:

值和数组的转换和拆箱存在奇怪的不一致:

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[]

为什么在禁止直接转换的情况下允许数组拆箱?

【问题讨论】:

这基本上是 C# 语言规则允许的内容和 CLR 允许的内容之间的区别。这是一个不幸的阻抗不匹配:( 它不是uint 大于int 因此它不应该工作,因为目前编译器没有每个成员的值 同意@JonSkeet。请注意,您可以举一个更糟糕的例子。比如,将us 的定义更改为new unit[] 3000000001u, 3000000002u, 3000000003u (选择的数字介于2**312**32 之间,这样它们对于没有符号的32 位整数来说是可以的,但对于32 位有符号整数)。 也尝试阅读Why does "int[] is uint[] == true" in C# 和其他线程,看看相关的惊喜也源于不幸的问题,即 C# 语言和 .NET 运行时不同意 int[] 和 @ 987654331@是否兼容。 尝试更好的链接:Why does "int[] is uint[] == true" in C# 【参考方案1】:

为什么在禁止直接转换的情况下允许数组拆箱?

如果你做(int[])(object)us,你就是在做两次转换,both of which are allowed by C#:

uint[]object(您可以将任何引用类型转换为object) 从objectint[](您可以从object 转换为任何引用类型)

因此(int[])(object)us 编译。

如果您执行(int[])us,您将执行从int[]uint[] 的单次转换,这是C# 不允许的。根据 C# 规范,如果 TU 是引用类型,则只能从 T[] 转换为 U[],并且您可以从 T 转换为 Uintuint 都不满足这两个条件,所以 (int[])us 不会编译。

在运行时,检查从uint[]int[] 的转换,看它是成功还是失败。 CLR 确实允许这样做。请参阅CLR spec 的第 I.8.7.1 节。

当且仅当以下至少一项成立时,签名类型 T 与签名类型 U 兼容。

[...]

T 是一个秩为 r 且元素类型为 V 的数组,U 是一个秩为 r 和元素类型为 W 相同的数组,V 是与 W 的数组元素兼容的数组。

[...]

签名类型 T 与签名类型 U 是数组元素兼容的,当且仅当 T 具有底层类型 V 并且 U 具有底层类型 W 并且:

    V 与 W 兼容;或 V 和 W 具有相同的缩减类型。

intuint的“缩减类型”都是int

这就是(int[])(object)us 在运行时成功的原因。

另请注意,将us 转换为object 不是“装箱”。这里的一切都是引用类型。您只是在更改引用的类型。

【讨论】:

您可以使用类似以下的方法来(向下)强制转换对数组类型的引用,而不允许出现以下情况:(A)运行时类型和被强制转换的类型是数组类型整数,但一个是有符号的,另一个是无符号的。 (B) 这两种类型涉及整数类型和枚举类型,或者两种不同的枚举类型。 (C) 强制转换仅对 C# 的 病态数组协方差 和运行时有效(数组协方差是“病态的”,因为数组是读写的,而不是只读的,所以协方差是没有声音)。这里: static T[] PedanticDowncastToArray<T>(object obj) if (obj is T[] arr && arr.GetType() == typeof(T[])) return arr; throw new ArgumentException($"The argument 'obj' is not precisely an instance of a one-dimensional array with element type 'typeof(T)'.", nameof(obj));

以上是关于为啥将数组拆箱为不正确的类型不会在 C# 中引发异常? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

C#数组的笔记

为啥 Clojure 编译器不会为不正确的类型提示抛出错误?

由C# dynamic是否装箱引发的思考

由c# dynamic是否装箱引发的思考

c# 泛型为啥能解决装箱拆箱问题

为啥我们需要在 C# 中装箱和拆箱?