无法将值类型数组转换为params对象
如果C#可以将int转换为对象,为什么不将int []转换为对象[]?
简单程序示例:
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"); } }
如果C#可以将int转换为对象,为什么不将int []转换为对象[]?
你的问题也可以说是“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#最糟糕的function”的候选人,但事实上确实有效 。
您的问题是“当源数组是值类型的数组且目标数组是引用类型的数组时,为什么数组协方差不起作用?”
因为这两件事在运行时有不同的forms 。 假设你有一个包含十个元素的byte[]
。 为数组元素保留的实际存储长度为十个字节。 假设您在64位计算机上,并且您有一个包含十个元素的object[]
。 存储空间大八倍!
显然,您无法通过引用转换将对10个字节的存储的引用转换为存储,以便对字节进行10个8字节的引用。 额外的七十个字节不是无处不在; 有人必须分配它们。
而且: 谁做拳击 ? 如果你有一个包含十个对象的数组,并且每个对象都是一个字节,那么这些字节中的每一个都被加框 。 但是字节数组中的字节没有加框。 那么当你进行转换时,谁做拳击?
通常在C#中, 协变转换始终保留表示 。 “动物参考”的表示与“长颈鹿参考”的表示完全相同。 但是“int”和“对象的引用”的表示完全不同。
人们期望将一种数组类型转换为另一种数组类型不会分配和复制一个巨大的数组 。 但是我们不能在十个字节的数组和一个包含十个引用的八十个字节的数组之间具有引用标识 ,因此整个事情只是非法。
现在,您可能会说,当值表示的表示相同时会发生什么? 事实上,这在C#中是非法的:
int[] x = new uint[10];
因为在C#中规则是只有仅涉及引用类型的协变数组转换才是合法的。 但是如果你强制它由运行时完成:
int[] x = (int[])(object) new uint[10];
然后运行时允许它,因为四字节int和四字节uint具有相同的表示。
如果你想更好地理解这一点,那么你应该阅读我关于协方差和逆变在C#中如何工作的整篇系列文章:
-
整个系列
-
不安全参考元素数组协方差的细节
-
有关值元素数组协方差的更多信息
的确,你不能转换它。 参考类型数组是协变的; 值类型数组不是 。 所以; 你将不得不使用以下之一:
一系列盒装值:
var b = new object[] {0,1};
或者您可以使用IList
:
static void AssertMoreThan1(IList v) { ... (check with .Count) }
或generics:
static void AssertMoreThan1(T[] v) { ... }
最后一个是我的偏好。