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


69

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

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

但不是值类型,因此如果更改stringint,将得到编译错误:

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#编译器的行为方式不同?


6
+1,您是来自这个问题
哈比卜(Habib)

@Habib:是的,它的灵感源于
13年

快速猜测,所有引用类型都为引用使用相同数量的字节,因此,它易于隐式转换。
Jodrell

老实说,我以前从未看过new[] { ... }语法。您确定这符合C#规范吗?
约翰·亚历克斯

1
@ ja72:它称为隐式类型数组。你可以在VS试试这个
cuongle

Answers:


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 };

因此,问题现在有效地变成了“为什么不能将an分配int[]给一个object[]”,就像您在问题末尾提到的那样。答案也很简单,如第1.109节(“数组协方差”)所示:

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


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 },由于类型推断的工作原理,它首先查看Right Side Side表达式并得出结论,该表达式new[] { 0, 1 }应视为new int[] { 0, 1 }。然后,它尝试将此int数组分配给对象数组,由于值类型的协变转换问题而产生错误。object[] list1 = { 0, 1 }不过,该声明使用了集合初始值设定项,在这种情况下,集合的类型就是定义该类型的地方,因此每个元素都将强制转换为集合所期望的类型。


10

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

如果是第一种情况:object[] list1 = { 0, 1 };很明显,应该创建一个集合。但是应该是哪种呢?new某处没有任何操作。只有一个提示:list1类型为object[]。因此,编译器将创建该集合并用值填充它。

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


2

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

您还可以显式地将原始类型装箱:

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

当您将'int []'或'Int32 []'指定为数组类型时,编译器不会为您暗中装箱,但似乎可以将其添加到C#中。


1

数组初始化程序是编译器的便利。如果我说“我正在声明一个对象数组并为其分配一个值”,则对于编译器,假设您{ 0, 1 }是一个对象数组并如此解释是合理的。尽管语法似乎是一种赋值,但不是:您正在使用初始化程序。此语法的简写为object[] list1 = new object[] { 0, 1 }

当您说时new[] { 0, 1 },这是一个创建数组并将其初始化的表达式。该表达式的计算与您为其分配的内容无关-并且由于编译器检测到隐式整数类型,因此它将创建一个int[]。该表达式的简化版本是object[] list2 = new int[] { 0, 1 }

如果比较这两个语句的简化版本,很明显可以看到它们的不同之处。


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

是的简写

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

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

当您说时new[],它等同于new int[],因此也适用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.