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

时间:2013-05-09 08:00:08

标签: c# arrays

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

6 个答案:

答案 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[],因此同样适用。