为什么C#在两个int数组语法中行为不同
C#中的数组是隐式引用types上的协variables :
object[] listString = new string[] { "string1", "string2" };
但不是值types,所以如果你改变string
为int
,你会得到编译错误:
object[] listInt = new int[] {0, 1}; // compile error
现在,关心的是当你声明int
数组就像下面的两个语法没有明确声明int
types,只是区分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
操作。 只有一个提示: list1
是object[]
的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[]
,因此同样适用。