C#中的数组是隐式引用类型的共同变体:
object[] listString = new string[] { "string1", "string2" };
但不是值类型,因此如果您将string
更改为int
,则会收到编译错误:
object[] listInt = new int[] {0, 1}; // compile error
现在,关注的是当您声明int
数组时,如下面的两个语法没有明确声明类型int
,只是区分new[]
,编译器会区别对待:< / p>
object[] list1 = { 0, 1 }; //compile successfully
object[] list2 = new[] {0, 1}; //compile error
您将成功编译object[] list1 = { 0, 1 };
,但编译错误object[] list2= new[] {0, 1};
。
似乎C#编译器对待
object[] list1 = { 0, 1 };
作为
object[] list1 = new object[]{ 0, 1 };
但
object[] list2 = new[] { 0, 1 };
作为
object[] list2 = new int[]{ 0, 1 }; //error because of co-variant
为什么C#编译器在这种情况下的行为方式不同?
答案 0 :(得分:57)
编译的版本使用数组初始化程序来初始化list1
。 C#语言规范§1.110(“数组初始值设定项”)声明:
数组初始值设定项由一系列变量初始值设定项组成, 由“{”和“}”标记括起来并用“,”标记分隔。每 变量初始值设定项是一个表达式,或者在一个表达式的情况下 多维数组,嵌套数组初始化器。
上下文中 使用数组初始值设定项确定数组的类型 被初始化。在数组创建表达式中,数组类型 紧接在初始化程序之前,或者从初始化程序推断出来 数组初始值设定项中的表达式。在字段或变量中 声明,数组类型是字段或变量的类型 声明。
在字段或变量中使用数组初始值设定项时 声明,例如:
int[] a = {0, 2, 4, 6, 8};
它只是等效数组创建表达式的简写:
int[] a = new int[] {0, 2, 4, 6, 8};
所以很明显这应该编译。
第二个版本使用显式的数组创建表达式,您可以在其中具体指示编译器要创建的数组类型。 §1.51.10.4(“数组创建表达式”)声明:
第三种形式的数组创建表达式称为 隐式输入数组创建表达式。它类似于 第二种形式,除了数组的元素类型不是 明确给出,但确定为最佳通用类型(§1.50.2.14) 数组初始值设定项中的表达式集。
因此,第二个版本相当于
object[] list2 = new int[] { 0, 1 };
所以现在问题实际上变成了“为什么我不能将int[]
分配给object[]
”,正如您在问题的最后提到的那样。答案也很简单,见§1.109(“数组协方差”):
数组协方差特别不扩展到数组 值类型。例如,不存在允许
int[]
的转换 被视为object[]
。
答案 1 :(得分:27)
声明
object[] listInt = new int[] {0, 1};
无效,因为值类型不允许协变数组转换(int
是值类型)。或者,声明
object[] listInt = new string[] {"0", "1"};
是有效的,因为引用类型允许协变数组转换。这是因为赋值x = (object)myString
只涉及一个简单的赋值,但y = (object)myInt
需要装箱操作。
现在讨论两个声明之间的区别。在声明object[] list2 = new[] { 0, 1 }
中,由于类型推断的工作方式,它首先查看右侧表达式,并得出结论:new[] { 0, 1 }
应该被视为new int[] { 0, 1 }
。然后它尝试将此int数组分配给对象数组,由于值类型问题的协变转换而产生错误。但是,声明object[] list1 = { 0, 1 }
使用集合初始值设定项,在这种情况下,集合的类型是定义类型的位置,因此每个元素都将转换为集合所期望的类型。
答案 2 :(得分:10)
当您使用{
和}
时,您使用了集合初始值设定项(请参阅:http://msdn.microsoft.com/en-us/library/vstudio/bb384062.aspx)。这些括号之间的值必须放在某处。因此必须创建一个集合。编译器将分析上下文以找出哪种集合。
如果是第一个:object[] list1 = { 0, 1 };
,很明显应该创建一个集合。但它应该是什么样的?某处没有new
操作。只有一个提示:list1
属于object[]
类型。因此编译器创建该集合并用valiues填充它。
在您的第二个示例object[] list1 = new[] { 0, 1 };
中,还有另一个提示:new[]
。这个提示明确地说:将会有一个数组。该数组没有类型,因此它将尝试通过分析值来查找数组的类型。这些都是int
,所以它会创建一个int
的数组并填充它。另一个提示object[]
完全被忽略,因为创建提示比应该分配的提示要重要得多。现在编译器想要将此数组分配给list1和BOOM:这不适合!
答案 3 :(得分:2)
语句object[] list1 = { 0, 1 };
编译,因为编译器足够聪明,知道您正在尝试将数值类型的数组转换为引用类型数组,因此它将Int32元素打包为引用类型。
您还可以明确地选中基元类型:
object[] list2 = Array.ConvertAll<int, Object>(new[] { 0, 1 }, input => (Object)input);
当你指定'int []'或'Int32 []'作为数组类型时,编译器不会隐式为你做装箱,但似乎可以将它添加到C#中。
答案 4 :(得分:1)
数组初始化器是编译器的便利。如果我说“我正在声明一个对象数组并为其赋值”,编译器认为你的{ 0, 1 }
是一个对象数组并将其解释为这样是合理的。虽然语法似乎是一个赋值,但它不是:您正在使用初始化器。这种语法的缩写是object[] list1 = new object[] { 0, 1 }
当你说new[] { 0, 1 }
时,这是一个创建数组并初始化它的表达式。此表达式的计算与您为其分配的内容无关 - 并且由于编译器检测到隐式整数类型,因此会创建int[]
。该表达式的缩写版本为object[] list2 = new int[] { 0, 1 }
如果你比较这两个陈述的简写版本,很清楚它们的不同之处。
答案 5 :(得分:1)
object[] listInt = new int[] {0, 1};
是
的简写object[] listInt;
listInt = new int[] {0, 1};
因int[]
is not covariant with object[]
而无效。
当你说new[]
时,它等同于new int[]
,因此同样适用。