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

本文关键字:语法 数组 两种 int 为什么 | 更新日期: 2023-09-27 18:12:53

C#中的数组是引用类型:上的隐式协变

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

但不在值类型上,所以如果您将string更改为int,您将得到编译错误:

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

现在,问题是,当您像下面的两个语法一样声明int数组时,它们并没有明确声明类型int,只是在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#编译器在这种情况下的行为方式不同?

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

编译的版本使用数组初始值设定项来初始化list1。C#语言规范§1.110("数组初始化程序"(规定:

数组初始化器由一系列变量初始化器组成,由"{"answers"}"标记包围,并由","标记分隔。每个变量初始值设定项是一个表达式,或者在多维数组,嵌套数组初始值设定项。

中的上下文使用哪个数组初始值设定项来确定数组的类型正在初始化。在数组创建表达式中,数组类型直接在初始值设定项之前,或从数组初始值设定项中的表达式。在字段或变量中声明,数组类型是要声明。

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

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[]的转换作为CCD_ 12处理。

声明

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

无效,因为值类型不允许协变数组转换(并且int是值类型(。或者,申报

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

是有效的,因为引用类型允许协变数组转换。这是因为分配x = (object)myString只涉及一个简单的分配,而y = (object)myInt需要装箱操作。

现在来看两个声明之间的区别。在声明object[] list2 = new[] { 0, 1 }中,由于类型推理的工作方式,它首先查看Right Hand Side表达式,并得出结论,new[] { 0, 1 }应被视为new int[] { 0, 1 }。然后,它试图将这个int数组分配给一个对象数组,由于值类型的协变转换问题,导致错误。然而,声明object[] list1 = { 0, 1 }使用了集合初始值设定项,在这种情况下,集合的类型是定义类型的地方,因此每个元素都将被强制转换为集合所需的类型。

使用{}时,将使用集合初始值设定项(请参见:http://msdn.microsoft.com/en-us/library/vstudio/bb384062.aspx)。这些括号之间的值必须放在某个地方。因此,必须创建一个集合。编译器将对上下文进行分析,以找出什么样的集合。

在第一种情况下:object[] list1 = { 0, 1 };,显然应该创建一个集合。但是它应该是什么样的呢?某个地方没有new操作。只有一个提示:list1属于object[]类型。所以编译器会创建该集合,并在其中填充有效值。

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

语句object[] list1 = { 0, 1 };之所以编译,是因为编译器足够聪明,可以知道您正试图将数字类型数组转换为引用类型数组,因此它将Int32元素框入引用类型。

您也可以显式地框显基元类型:

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

当您指定"int[]"或"Int32[]"作为数组类型时,编译器不会隐式为您进行装箱,但这似乎可以添加到C#中。

数组初始化程序是编译器的便利工具。如果我说"我正在声明一个对象数组并为其赋值",那么编译器可以合理地假设{ 0, 1 }是一个对象阵列并将其解释为对象数组。尽管语法看起来是一个赋值,但事实并非如此:您使用的是初始化程序。此语法的惯用手是object[] list1 = new object[] { 0, 1 }

当你说new[] { 0, 1 }时,这是一个创建数组并对其进行初始化的表达式。这个表达式的计算与你将其分配给的对象无关,因为编译器检测到隐式整数类型,所以它会创建一个int[]。该表达式的长期版本是object[] list2 = new int[] { 0, 1 }

如果你比较一下这两种说法的长期版本,就会清楚地看到它们的不同之处。

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

是的简写

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

由于CCD_ 38与CCD_。

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