C#6.0语言规范(十二) 数组
数组是一种数据结构,包含许多通过计算索引访问的变量。包含在数组中的变量(也称为数组的元素)都是相同的类型,这种类型称为数组的元素类型。
数组具有确定与每个数组元素相关联的索引数的等级。数组的等级也称为数组的维度。秩为1的数组称为一维数组。秩大于1的数组称为多维数组。特定大小的多维阵列通常被称为二维阵列,三维阵列等。
数组的每个维度具有相关联的长度,该长度是大于或等于零的整数。维度长度不是数组类型的一部分,而是在运行时创建数组类型的实例时建立的。维度的长度决定了该维度的索引的有效范围:对于长度维度N
,索引的范围可以从包含0
到N - 1
包含。数组中元素的总数是数组中每个维度的长度的乘积。如果数组的一个或多个维度的长度为零,则称该数组为空。
数组的元素类型可以是任何类型,包括数组类型。
数组类型
数组类型写为non_array_type,后跟一个或多个rank_specifier:
1 array_type 2 : non_array_type rank_specifier+ 3 ; 4 5 non_array_type 6 : type 7 ; 8 9 rank_specifier 10 : '[' dim_separator* ']' 11 ; 12 13 dim_separator 14 : ',' 15 ;
一个non_array_type是任何类型本身不是一个ARRAY_TYPE。
数组类型的秩由最左边的给定rank_specifier在ARRAY_TYPE:甲rank_specifier表示该阵列是具有一个的秩加的“数量的阵列,
”中的令牌rank_specifier。
数组类型的元素类型是删除最左边的rank_specifier所产生的类型:
- 表单的数组类型
T[R]
是具有rankR
和非数组元素类型的数组T
。 - 表单的数组类型
T[R][R1]...[Rn]
是具有rankR
和元素类型的数组T[R1]...[Rn]
。
实际上,在最终的非数组元素类型之前从左到右读取rank_specifier。该类型int[][,,][,]
是二维数组的三维数组的一维数组int
。
在运行时,数组类型的值可以是null
对该数组类型的实例的引用。
System.Array类型
类型System.Array
是所有数组类型的抽象基类型。任何数组类型都存在隐式引用转换(隐式引用转换)System.Array
,并且存在从任何数组类型到显式引用转换(显式引用转换)System.Array
。请注意,System.Array
它本身不是array_type。相反,它是一个class_type,从中派生所有array_type。
在运行时,type的值System.Array
可以是null
或者对任何数组类型的实例的引用。
数组和通用IList接口
一维数组T[]
实现接口System.Collections.Generic.IList<T>
(IList<T>
简称)及其基接口。因此,存在从其T[]
到IList<T>
基本接口的隐式转换。此外,如果有一个隐式引用转换从S
到T
然后S[]
器具IList<T>
并且存在隐式引用转换从S[]
到IList<T>
及其基接口(隐式引用转换)。如果存在明确的引用转换S
,T
则会有一个显式的引用转换S[]
,IList<T>
以及它的基接口(显式引用转换)。例如:
1 using System.Collections.Generic; 2 3 class Test 4 { 5 static void Main() { 6 string[] sa = new string[5]; 7 object[] oa1 = new object[5]; 8 object[] oa2 = sa; 9 10 IList<string> lst1 = sa; // Ok 11 IList<string> lst2 = oa1; // Error, cast needed 12 IList<object> lst3 = sa; // Ok 13 IList<object> lst4 = oa1; // Ok 14 15 IList<string> lst5 = (IList<string>)oa1; // Exception 16 IList<string> lst6 = (IList<string>)oa2; // Ok 17 } 18 }
赋值lst2 = oa1
生成编译时错误,因为从object[]
to 转换IList<string>
是显式转换,而不是隐式转换。强制转换(IList<string>)oa1
将导致在运行时抛出异常,因为oa1
引用object[]
而不是a string[]
。但是,(IList<string>)oa2
由于oa2
引用a ,强制转换不会引发异常string[]
。
每当有来自的隐式或显式引用转换S[]
时IList<T>
,还有一个显式的引用转换IList<T>
及其基本接口S[]
(显式引用转换)。
当数组类型S[]
实现时IList<T>
,实现的接口的某些成员可能会抛出异常。接口实现的精确行为超出了本规范的范围。
数组创建
数组实例由array_creation_expression(数组创建表达式)或包含array_initializer(数组初始值设定项)的字段或局部变量声明创建。
创建数组实例时,将建立每个维度的等级和长度,然后在实例的整个生命周期内保持不变。换句话说,不可能更改现有数组实例的等级,也不可能调整其维度的大小。
数组实例始终是数组类型。该System.Array
类型是无法实例化的抽象类型。
由array_creation_expression创建的数组元素始终初始化为其默认值(默认值)。
数组元素访问
阵列元件使用访问element_access表达式(数组访问的形式的)A[I1, I2, ..., In]
,其中A
是一个数组类型的表达式和每一个Ix
是类型的表达式int
,uint
,long
,ulong
,或可隐式转换到一个或多个这些类型的。数组元素访问的结果是一个变量,即由索引选择的数组元素。
可以使用foreach
语句(foreach语句)枚举数组的元素。
数组成员
每个数组类型都继承该类型声明的成员System.Array
。
数组协方差
对于任何两个reference_type小号A
和B
,如果隐式引用转换(隐式引用转换)或显式引用转换(显式引用转换)存在于从A
到B
,则相同的参考转换同时从阵列型存在A[R]
于阵列类型B[R]
,其中R
在任何给定rank_specifier(但两种数组类型都相同)。这种关系称为阵列协方差。在特定的阵列协方差意味着数组类型的值A[R]
实际上可以是一个数组类型的实例的引用B[R]
,提供了一种隐式引用转换从存在B
于A
.
由于数组协方差,对引用类型数组元素的赋值包括运行时检查,该运行时检查确保分配给数组元素的值实际上是允许类型(简单赋值)。例如:
1 class Test 2 { 3 static void Fill(object[] array, int index, int count, object value) { 4 for (int i = index; i < index + count; i++) array[i] = value; 5 } 6 7 static void Main() { 8 string[] strings = new string[100]; 9 Fill(strings, 0, 100, "Undefined"); 10 Fill(strings, 0, 10, null); 11 Fill(strings, 90, 10, 0); 12 } 13 }
分配给array[i]
在Fill
方法隐含地包括一个运行时检查,以确保通过引用的对象value
是任一null
或一个实例,其与实际的元件类型兼容array
。在Main
,前两次调用Fill
成功,但第三次调用导致System.ArrayTypeMismatchException
在执行第一次赋值时抛出array[i]
。发生异常是因为盒装int
不能存储在string
数组中。
数组协方差特别不扩展到value_type s的数组。例如,不存在允许int[]
将其视为的转换object[]
。
数组初始化器
可以在字段声明(字段),局部变量声明(局部变量声明)和数组创建表达式(数组创建表达式)中指定数组初始值设定项:
1 array_initializer 2 : '{' variable_initializer_list? '}' 3 | '{' variable_initializer_list ',' '}' 4 ; 5 6 variable_initializer_list 7 : variable_initializer (',' variable_initializer)* 8 ; 9 10 variable_initializer 11 : expression 12 | array_initializer 13 ;
数组初始值设定项由一系列变量初始值设定项组成,由“ {
”和“ }
”标记括起,并用“ ”标记分隔,
。每个变量初始值设定项都是一个表达式,或者在多维数组的情况下,是嵌套数组初始值设定项。
使用数组初始值设定项的上下文确定要初始化的数组的类型。在数组创建表达式中,数组类型紧接在初始化程序之前,或者从数组初始值设定项中的表达式推断出来。在字段或变量声明中,数组类型是声明的字段或变量的类型。在字段或变量声明中使用数组初始值设定项时,例如:
int[] a = {0, 2, 4, 6, 8};
它只是等效数组创建表达式的简写:
int[] a = new int[] {0, 2, 4, 6, 8};
对于一维数组,数组初始值设定项必须由一系列表达式组成,这些表达式与数组的元素类型兼容。表达式以递增的顺序初始化数组元素,从索引为零的元素开始。数组初始值设定项中的表达式数决定了要创建的数组实例的长度。例如,上面的数组初始值设定项创建int[]
长度为5 的实例,然后使用以下值初始化实例:
a[0] = 0; a[1] = 2; a[2] = 4; a[3] = 6; a[4] = 8;
对于多维数组,数组初始值设定项必须具有与数组中的维度一样多的嵌套级别。最外面的嵌套级别对应于最左边的维度,最里面的嵌套级别对应于最右边的维度。数组的每个维度的长度由数组初始值设定项中相应嵌套级别的元素数量确定。对于每个嵌套数组初始值设定项,元素数必须与同一级别的其他数组初始值设定项相同。这个例子:
int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};
创建一个二维数组,最左边的维度长度为5,最右边的维度长度为2:
int[,] b = new int[5, 2];
创建一个二维数组,最左边的维度长度为5,最右边的维度长度为2:
int[,] b = new int[5, 2];
然后使用以下值初始化数组实例:
1 b[0, 0] = 0; b[0, 1] = 1; 2 b[1, 0] = 2; b[1, 1] = 3; 3 b[2, 0] = 4; b[2, 1] = 5; 4 b[3, 0] = 6; b[3, 1] = 7; 5 b[4, 0] = 8; b[4, 1] = 9;
如果给出了最右边的维度,其长度为零,则假设后续维度的长度为零。这个例子:
int[,] c = {};
为最左边和最右边的维度创建一个长度为零的二维数组:
int[,] c = new int[0, 0];
当数组创建表达式包括显式维度长度和数组初始值设定项时,长度必须是常量表达式,并且每个嵌套级别的元素数量必须与相应的维度长度匹配。这里有些例子:
1 int i = 3; 2 int[] x = new int[3] {0, 1, 2}; // OK 3 int[] y = new int[i] {0, 1, 2}; // Error, i not a constant 4 int[] z = new int[3] {0, 1, 2, 3}; // Error, length/initializer mismatch
这里,初始化程序y
导致编译时错误,因为维度长度表达式不是常量,并且初始化程序z
导致编译时错误,因为初始化程序中的元素的长度和数量不一致。