一尘不染

无法将值类型数组转换为params对象[]

c#

如果C#可以将int强制转换为对象,为什么不将int []强制转换为object []?

简单程序示例:

void Main()
{
    var a = new String[]{"0", "1"};
    var b = new int[]{0, 1};

    AssertMoreThan1(a); // No Exception
    AssertMoreThan1(b); // Exception
}

static void AssertMoreThan1(params object[] v){
    if(v.Length == 1){
        throw new Exception("Too Few Parameters");
    }
}

阅读 372

收藏
2020-05-19

共1个答案

一尘不染

如果C#可以将int强制转换为对象,为什么不将int []强制转换为object []?

您的问题也可以表述为“ C#中数组转换的 协方差 规则是什么?”

它们有些棘手,以几种有趣和不幸的方式破坏了它们。

首先,我们应该清楚地说明“协方差”的含义。协方差是 映射 保留 关系 的属性。这里的映射是“ 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();

因为哺乳动物 实际上是长颈鹿的阵列

这意味着,每次 写入 其元素为 未密封引用类型 的数组时,运行时都会执行类型检查, 并且如果类型检查失败 ,则运行时 可能会崩溃

这是我的“ C#最差功能”的候选人,但实际上确实 有效

您的问题是“当源数组是值类型的数组而目标数组是引用类型的数组时,为什么数组协方差不起作用?”

因为 这两件事在运行时具有不同的形式
。假设您有一个byte[]包含十个元素的。为数组元素保留的实际存储空间为十个字节长。假设您使用的是64位计算机,并且有一个object[]包含10个元素的计算机。存储空间大八倍!

显然,您 不能通过引用转换 将对十个字节的存储 引用转换 为对十个八字节字节的存储引用。多余的70个字节不会无处不在。有人必须分配它们。

此外: 谁拳击 ?如果您有一个由十个对象组成的数组,并且每个对象都是一个字节,则将这些字节中的每一个 装箱
。但是字节数组中的字节未装箱。因此,当您进行转换时,谁将进行拳击?

通常,在C#中, 协变转换始终保留表示形式 。“提及动物”的表示与“提及长颈鹿”的表示完全相同。但是“ int”和“对对象的引用”的表示形式完全不同。

人们期望将一种数组类型转换为另一种数组类型不会 分配和复制巨大的数组 。但是我们不能在包含十个字节的数组和包含十个引用的八十个字节的数组之间具有
引用身份 ,因此,整个事情都被视为非法。

现在,您可能会说,嗯,当值类型的表示 形式相同时 会发生什么?实际上,这在C#中是非法的:

int[] x = new uint[10];

因为在C#中,规则是仅涉及引用类型的协变数组转换才是合法的。但是,如果您强迫它在运行时完成:

int[] x = (int[])(object) new uint[10];

然后运行时允许它,因为一个四字节的int和一个四字节的uint具有相同的表示形式。

如果您想更好地理解这一点,那么您可能应该阅读我的整篇有关协方差和逆方差如何在C#中工作的文章:

2020-05-19