如果 C# 可以将 int 转换为对象,为什么不能将 int[] 转换为 object[]?
您的问题也可以表述为“什么是协方差C# 中数组转换的规则?”
它们有点棘手,并且以几种有趣和不幸的方式被破坏。
首先,我们应该清楚地说明“协方差”的含义。协方差是一个属性mapping保留了一个关系。这里的映射是“T 到 T 的数组”。这关系是“可以隐式转换”。例如:
Giraffe
可以隐式转换为Mammal
.
这是两种类型之间的关系。现在将映射应用到关系的两侧:
Giraffe[]
可以转换为Mammal[]
.
如果第一个陈述的真实性总是蕴含第二个陈述的真实性——也就是说,如果映射果酱关系的真实性——那么映射被称为“协变”。
作为简写,我们不说“从 T 到 T 的数组的映射是隐式转换关系上的协变映射”,而是只说“数组是协变的”,并希望其余部分可以从上下文中理解。
好的,现在我们已经有了定义:数组带有引用类型元素在 C# 中是协变的。可悲的是,这是破坏协方差的:
class Mammal {}
class Giraffe : Mammal {}
class Tiger : Mammal {}
...
Mammal[] mammals = new Giraffe[1];
这是完全合法的,因为引用类型元素的数组在 C# 中是协变的。但是这会在运行时崩溃:
mammals[0] = new Tiger();
因为哺乳动物是真的是一群长颈鹿.
这意味着每次你write到一个数组,其元素是未密封的引用类型,运行时执行类型检查如果类型检查失败可能会崩溃.
这是我认为的“C# 最差功能”,但事实上它确实如此work.
您的问题是“当源数组是值类型数组而目标数组是引用类型数组时,为什么数组协方差不起作用?”
Because 这两件事在运行时有不同的形式。假设你有一个byte[]
有十个元素。为数组元素保留的实际存储空间为十个字节长。假设您使用的是 64 位计算机,并且您有object[]
有十个元素。存储空间扩大了八倍!
显然你无法转换通过引用转换对十个字节的存储的引用对十个八字节的存储的引用。额外的 70 个字节并不是凭空产生的;而是有的。必须有人分配它们。
而且:谁打拳击?如果您有一个包含十个对象的数组,并且每个对象都是一个字节,那么这些字节中的每一个都是boxed。但字节数组中的字节没有装箱。那么当你进行转换时,谁来进行拳击呢?
一般来说,在 C# 中,协变转换始终保留表示形式。 “对动物的引用”的表示与“对长颈鹿的引用”的表示完全相同。但“int”和“对象引用”的表示完全不同。
人们期望将一种数组类型转换为另一种数组类型不会分配并复制一个巨大的数组。但我们不能拥有参考身份介于十个字节的数组和包含十个引用的八十个字节的数组之间,因此整个事情都是非法的。
现在,你可能会说,好吧,当表征是相同的对于值类型?事实上,这在 C# 中是非法的:
int[] x = new uint[10];
因为在 C# 中,规则是只有仅涉及引用类型的协变数组转换才是合法的。但如果你强制它由运行时完成:
int[] x = (int[])(object) new uint[10];
然后运行时允许它,因为四字节 int 和四字节 uint 具有相同的表示形式。
如果您想更好地理解这一点,那么您可能应该阅读我关于 C# 中协变和逆变如何工作的整个系列文章:
整个系列
不安全参考元素数组协方差的细节
有关值元素数组协方差的更多信息