为什么C#在两个int数组语法中行为不同

C#中的数组是隐式引用types上的协variables

object[] listString = new string[] { "string1", "string2" }; 

但不是值types,所以如果你改变stringint ,你会得到编译错误:

 object[] listInt = new int[] {0, 1}; // compile error 

现在,关心的是当你声明int数组就像下面的两个语法没有明确声明inttypes,只是区分new[] ,编译器会以不同的方式处理:

 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#编译器在这种情况下以不同的方式运行?

编译的版本使用数组初始化器来初始化list1 。 C#语言规范§1.110(“数组初始化器”)指出:

数组初始值设定项由一系列variables初始值设定项组成,由“{”和“}”标记包围,并用“,”标记分隔。 每个variables初始值设定项都是一个expression式,或者在multidimensional array的情况下,是一个嵌套数组初始值设定项。

使用数组初始值设定项的上下文决定了正在初始化的数组的types。 在数组创buildexpression式中,数组types立即在初始值设定项之前,或者从数组初始值设定项中的expression式推断出来。 在字段或variables声明中,数组types是声明的字段或variables的types。

在数组初始值设定项用于字段或variables声明中时,如:

 int[] a = {0, 2, 4, 6, 8}; 

它只是简单的等效数组创buildexpression式:

 int[] a = new int[] {0, 2, 4, 6, 8}; 

所以很明显,这应该编译。

第二个版本使用一个显式的数组创buildexpression式 ,在这里你指定编译器具体指定要创build的数组types。 §1.51.10.4(“数组创buildexpression式”)指出:

第三种forms的数组创buildexpression式被称为隐式types数组创buildexpression式 。 它与第二种forms类似,不同之处在于数组的元素types没有明确给出,而是被确定为数组初始值设定项中expression式集的最佳公共types(第1.50.2.14节)。

因此,第二个版本相当于

 object[] list2 = new int[] { 0, 1 }; 

所以这个问题现在变成了“为什么我不能把一个int[]赋给一个object[] ”,正如你在问题结尾提到的一样。 答案也很简单,见§1.109(“数组协方差”):

数组协方差特别不扩展到数值型数组。 例如,不存在允许将int[]视为object[]

声明

 object[] listInt = new int[] {0, 1}; 

是无效的,因为对于值types不允许协变数组转换(而int是一个值types)。 或者,声明

 object[] listInt = new string[] {"0", "1"}; 

是有效的,因为参考types允许协变数组转换。 这是因为赋值x = (object)myString只涉及一个简单的赋值,但y = (object)myInt需要一个装箱操作。

现在谈谈这两个声明的区别。 在声明object[] list2 = new[] { 0, 1 } ,由于types推理的工作原理,它首先查看右手边expression式并得出结论: new[] { 0, 1 }应该被当作new int[] { 0, 1 } 。 然后它试图将这个int数组赋给一个对象数组,由于值types问题的协变转换而给出一个错误。 然而,声明object[] list1 = { 0, 1 }使用集合初始值设定项,在这种情况下,集合的types就是定义types的地方,所以每个元素将被转换为集合所期望的types。

当您使用{} ,您使用集合初始值设定项(请参阅: http : //msdn.microsoft.com/en-us/library/vstudio/bb384062.aspx )。 这些括号之间的值将不得不放在某个地方。 为此,必须创build一个集合。 编译器将通过上下文来找出什么样的集合。

如果第一个: object[] list1 = { 0, 1 }; 很明显应该有一个集合创build。 但是,应该是什么样的? 某处没有new操作。 只有一个提示: list1object[]的types。 所以编译器创build了这个集合,并用它来填充它。

在你的第二个例子中, object[] list1 = new[] { 0, 1 }; 还有另外一个提示: new[] 。 这个提示明确地说:将会有一个数组。 该数组没有types,所以它会尝试通过数值来查找数组的types。 这些都是int ,所以它会创build一个int数组并填充它。 另一个提示object[]完全被忽略,因为创build的提示比应该分配的提示要重要得多。 现在编译器想把这个数组分配给list1和BOOM:不适合!

语句object[] list1 = { 0, 1 }; 编译,因为编译器足够聪明,知道你正在试图将数组types的数组转换为引用types的数组,因此它将Int32元素放入引用types中。

您也可以显式地框出原始types:

object[] list2 = Array.ConvertAll<int, Object>(new[] { 0, 1 }, input => (Object)input);

当你指定'int []'或'Int32 []'作为数组types时,编译器不会隐式地为你做装箱,但是看起来好像可以将它添加到C#中。

数组初始化是一个编译器方便。 如果我说“我正在声明一个对象数组并给它赋值”,那么编译器就认为你的{ 0, 1 }是一个对象数组并对其进行解释是合理的。 虽然语法似乎是一个任务,但它不是:您正在使用一个初始化程序。 这个语法的代表是object[] list1 = new object[] { 0, 1 }

当你说new[] { 0, 1 } ,这是一个expression式,它创build一个数组并初始化它。 这个expression式是独立于你赋值的 – 而且由于编译器检测到隐式整型,所以它创build一个int[] 。 该expression式的longhand版本是object[] list2 = new int[] { 0, 1 }

如果您比较这两个陈述的长期版本,很明显看到它们的不同之处。

 object[] listInt = new int[] {0, 1}; 

是速记

 object[] listInt; listInt = new int[] {0, 1}; 

这不起作用,因为int[]不是与object[]协变的 。

而当你说new[] ,它相当于new int[] ,因此同样适用。