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

cuo*_*gle 69 c# arrays

C#中的数组是隐式引用类型的共变体:

object[] listString = new string[] { "string1", "string2" };
Run Code Online (Sandbox Code Playgroud)

但不是值类型,所以如果你string改为int,你会得到编译错误:

object[] listInt = new int[] {0, 1}; // compile error
Run Code Online (Sandbox Code Playgroud)

现在,值得关注的是,当您声明int数组时,如下面的两个语法没有显式声明类型int,只是区分开来new[],编译器会区别对待:

object[] list1 = { 0, 1 };       //compile successfully
object[] list2 = new[] {0, 1};    //compile error
Run Code Online (Sandbox Code Playgroud)

您将object[] list1 = { 0, 1 };成功编译,但object[] list2= new[] {0, 1};编译错误.

似乎C#编译器对待

object[] list1 = { 0, 1 };
Run Code Online (Sandbox Code Playgroud)

object[] list1 = new object[]{ 0, 1 };
Run Code Online (Sandbox Code Playgroud)

object[] list2 = new[] { 0, 1 };
Run Code Online (Sandbox Code Playgroud)

object[] list2 = new int[]{ 0, 1 };  //error because of co-variant
Run Code Online (Sandbox Code Playgroud)

为什么C#编译器在这种情况下的行为方式不同?

Jon*_*Jon 57

编译的版本使用数组初始值设定项进行初始化list1.C#语言规范§1.110("数组初始值设定项")指出:

数组初始值设定项由一系列变量初始值设定项组成,由"{"和"}"标记括起,并用","标记分隔.每个变量初始值设定项是一个表达式,或者在多维数组的情况下,是嵌套数组初始值设定项.

使用数组初始化程序的上下文确定要初始化的数组的类型.在数组创建表达式中,数组类型紧接在初始化程序之前,或者从数组初始值设定项中的表达式推断出来.在字段或变量声明中,数组类型是声明的字段或变量的类型.

在字段或变量声明中使用数组初始值设定项时,例如:

int[] a = {0, 2, 4, 6, 8};
Run Code Online (Sandbox Code Playgroud)

它只是等效数组创建表达式的简写:

int[] a = new int[] {0, 2, 4, 6, 8};
Run Code Online (Sandbox Code Playgroud)

所以很明显这应该编译.

第二个版本使用显式数组创建表达式,您可以在其中具体指示编译器要创建的数组类型.§1.51.10.4("数组创建表达式")指出:

第三种形式的数组创建表达式称为 隐式类型的数组创建表达式.它与第二种形式类似,不同之处在于未明确给出数组的元素类型,而是确定为数组初始值设定项中表达式集的最佳公共类型(§1.50.2.14).

因此,第二个版本相当于

object[] list2 = new int[] { 0, 1 };
Run Code Online (Sandbox Code Playgroud)

所以现在的问题实际上变成了"为什么我不能分配int[]给一个object[]",正如你在问题的最后提到的那样.答案也很简单,在§1.109("数组协方差")中给出:

数组协方差特别不扩展到值类型的数组.例如,不存在允许int[] 将其视为的转换object[].


eri*_*len 27

声明

object[] listInt = new int[] {0, 1};
Run Code Online (Sandbox Code Playgroud)

因为值类型不允许协变数组转换(并且int是值类型),因此无效.或者,声明

object[] listInt = new string[] {"0", "1"};
Run Code Online (Sandbox Code Playgroud)

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

现在来看两个声明之间的区别.在声明中object[] list2 = new[] { 0, 1 },由于类型推断的工作方式,它首先查看右侧的表达式,并得出结论new[] { 0, 1 }应该被视为new int[] { 0, 1 }.然后它尝试将此int数组分配给对象数组,由于值类型问题的协变转换而产生错误.object[] list1 = { 0, 1 }但是,声明使用集合初始值设定项,在这种情况下,集合的类型是定义类型的位置,因此每个元素都将转换为集合所期望的类型.


Mar*_*der 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's并填充它的数组.另一个提示object[]完全被忽略,因为创建提示比应该分配的提示要重要得多.现在编译器想要将此数组分配给list1和BOOM:这不适合!