返回目录
文中会引用Common Language Infrastructure (CLI) 标准的内容,如果你想亲自看一下CLI标准的内容。可以在这里下载:http://www.ecma-international.org/publications/standards/Ecma-335.htm
返回目录
首先,从最上面的层次看,编译器和C#语言一同隐藏了许多CLR数组繁琐的操作,以至于很多时候我们根本没必要去研究CLR怎样去操作数组,当然也有简单C#数组理论实在无法解释的现象,此时就必须去研究更底层的逻辑来解释。来看下面C#代码:
//创建一个二维的不以0开始的数组
//int[11-12, 101-103]
Array arr2= Array.CreateInstance(typeof(int),new int[] { 2,3 }, new int[] {11, 101 });
//转换成功
int[,] a2= (int[,])arr2;
//创建一个一维的不以0开始的数组
//int[1-3]
Array arr1= Array.CreateInstance(typeof(int),new int[] { 3 },new int[] { 1 });
//无法转换成功:抛出异常:无法将'System.Int32[*]' 转换成 'System.Int32[]'
int[] a1= (int[])arr1;
好,读者别急,问题会慢慢解开的,我们先来看看CLR对任何数组的支持。
我们来看一下CLI标准的说明(根据CLI标准 Partition II Metadata - 14.1和14.2):
CLR会为每一个数组添加3个公有成员函数:Get,Set和Address。(注意这是程序执行后CLR为其添加的成员,而不是.NET Framework中Array类定义的。因此在编译器里无法直接静态访问)
其中Get和Set功能和Array.GetValue和SetValue一样,Address用来返回数组成员的引用。
这些方法也可以通过运行后进行反射来得到:
//一维数组
Console.WriteLine(typeof(string[]).GetMethod("Get"));
Console.WriteLine(typeof(string[]).GetMethod("Set"));
Console.WriteLine(typeof(string[]).GetMethod("Address"));
//多维数组(5维)
Console.WriteLine(typeof(string[, , , ,]).GetMethod("Get"));
Console.WriteLine(typeof(string[, , , ,]).GetMethod("Set"));
Console.WriteLine(typeof(string[, , , ,]).GetMethod("Address"));
输出:
System.String Get(Int32)
Void Set(Int32, System.String)
System.String& Address(Int32)
System.String Get(Int32, Int32, Int32, Int32, Int32)
Void Set(Int32, Int32, Int32, Int32, Int32, System.String)
System.String& Address(Int32, Int32, Int32, Int32, Int32)
同时,CLR还为数组定义一些构造函数,用来指定数组创建的维数和每维的起始索引值。这些构造函数跟Array.CreateInstance方法类似。
比如下面的例子是CLI标准提供的例子:
//CIL标准70页
.locals (class [mscorlib]System.String[,] myArray)
ldc.i4.5 // load lower bound for dim 1
ldc.i4.6 // load (upper bound - lower bound + 1) for dim 1
ldc.i4.3 // load lower bound for dim 2
ldc.i4.5 // load (upper bound - lower bound + 1) for dim 2
newobj instance voidstring[,]::.ctor(int32, int32, int32, int32)
stloc myArray
返回目录
CLR中有一种数组是被特殊照顾的,那就是以0开始的一维数组。CLI标准文中以“Vector”称这种数组,在其他地方也常常被简称为“SZ Array”(single-dimensioned, zero-based array)。
这种数组除了包含有上述其他CLR数组类型具备的特点,还有如下被特殊赋予的性质:
那就是CIL(MSIL)语言的直接支持:CIL中的newarr, ldelem, stelem, 和ldelema指令都是只针对以0开始的一维数组!
原因很简单,这种数组是最常见的数组,因此CIL定义一些指令专门针对此类数组。
返回目录
OK,了解了CLR数组,我们开始看C#数组是如何执行CLR数组的。先来看被CIL直接支持的以0开始的一维数组。
下面C#代码,分别用C#数组声明方式和Array.CreateInstance方法来创建一个一维数组并对数组的元素进行赋值。
C#代码:
//创建一维数组
staticvoid arr1a()
{
int[] arr= newint[5];
arr[0]= 123;
}
//创建一维数组,Array.CreateInstance
staticvoid arr1b()
{
Array arr= Array.CreateInstance(typeof(int),5);
arr.SetValue(123,0);
//转换成int[]
int[] arr2= (int[])arr;
arr2[1]= 456;
}
接着看方法arr1a的IL:
.locals init (
[0] int32[] arr) //声明int[] arr本地变量
L_0000: ldc.i4.5
L_0001: newarr int32 //arr = new int[5];
L_0006: stloc.0
L_0007: ldloc.0 //arr进栈
L_0008: ldc.i4.0 //0进栈
L_0009: ldc.i4.s0x7b //123进栈
L_000b: stelem.i4 //arr[0] = 123
可以看出来,
由于这个数组是“以0开始的一维数组”,因此数组初始化是直接用newarr指令来进行的。
最后赋值用stelem指令,将数组索引值为0的成员赋值成123,同样stelem指令也是只针对“以0开始的一维数组”!
接着看arr1b的IL:
.locals init (
[0]class [mscorlib]System.Array arr,
[1] int32[] arr2)
L_0000: ldtoken int32
L_0005: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
//从RuntimeTypeHandle得到Type,这是typeof的IL
L_000a: ldc.i4.5
L_000b: call class [mscorlib]System.Array [mscorlib]System.Array::CreateInstance(class [mscorlib]System.Type, int32)
L_0010: stloc.0
L_0011: ldloc.0
L_0012: ldc.i4.s0x7b //123进栈
L_0014: box int32
L_0019: ldc.i4.0
L_001a: callvirt instance void [mscorlib]System.Array::SetValue(object, int32) //用Array.SetValue赋值
L_001f: ldloc.0
L_0020: castclass int32[] //将Array对象转换成int[]
L_0025: stloc.1
L_0026: ldloc.1
L_0027: ldc.i4.1
L_0028: ldc.i40x1c8 //456进栈
L_002d: stelem.i4 //用stelem赋值
arr1b方法首先用Array.CreateInstance方法来构造一个数组,接着用Array.SetValue方法对数组成员进行赋值。当然,由于这个数组是“以0开始的一维数组”,因此我们可以将这个Array对象转换称int[](用castclass IL指令),并用stelem IL又对arr[i]进行赋值。
我们可以在arr1a或arr1b方法中加入一行Console.WriteLine(arr.GetType()); 答案显而易见是:System.Int32[]
那么xxx[]类型到底代表什么?(xxx一个类型名称,是Type对象的FullName属性)
xxx[]代表一个“以0开始的一维数组”!(因此它不能代表所有的),可以这样理解
//xxx代表某类型
//这个数组是以0开始的一维数组
xxx[] array;
//N代表一个数字
//这里用newarr
array =new xxx[N];
//N代表一个数字
//这里用ldelem
xxx item= array[N];
//这里用stelem
array[N] = xxxObject;
返回目录
那么这里就是说不以0做起始索引值的一维数组。这里只能用Array.CreateInstance来创建,这种数组的GetType返回的类型是xxx[*],它是无法被转换成xxx[]的,因为xxx[]只代表“以0做起始值的一维数组”(如果用Array.CreateInstance创建一个以0开始的一维数组,也是可以转换的)。这类数组只能用Array类的GetValue和SetValue方法。
这里便可以解释上文提到的无法转换的问题:
//创建一个一维的不以0开始的数组
//int[1-3]
Array arr1= Array.CreateInstance(typeof(int),new int[] { 3 },new int[] { 1 });
Console.WriteLine(arr1.GetType());
//输出System.Int32[*]
//无法转换:抛出异常:无法将'System.Int32[*]' 转换成 'System.Int32[]'
//int[]必须是以0开始的一维数组!
int[] a1= (int[])arr1;
返回目录
这里就拿二维数组做示例。
来看下面C#代码:
//创建二维数组
staticvoid arr2a()
{
int[,] arr= newint[2,3];
arr[0,0] =123;
}
arr2a的IL
.locals init (
[0] int32[0...,0...] arr)
L_0000: ldc.i4.2
L_0001: ldc.i4.3
L_0002: newobj instance void int32[0...,0...]::.ctor(int32, int32) //调用CLR定义的构造函数
L_0007: stloc.0
L_0008: ldloc.0 //arr进栈
L_0009: ldc.i4.0//索引值进栈
L_000a: ldc.i4.0
L_000b: ldc.i4.s0x7b
L_000d: call instance void int32[0...,0...]::Set(int32, int32, int32) //调用CLR定义的Set函数
OK,多维数组肯定不属于不是CIL支持的“以0开始的一维数组”,因此它用不了newarr,stelem等指令,只能自己调用CLR定义的构造函数和上面提到过的Get和Set方法来操作数组,同样,多维数组是不是以0开始也无所谓了,都会被一视同仁!因为CLR只对以0开始的一维数组感兴趣。所以多维数组间不是以0开始的数组也可以转换成xxx[,]
代码解释:
//创建二维数组:[1-2][1-3]
Array arr= Array.CreateInstance(typeof(int),new int[] { 2,3 }, new int[] {1, 1 });
//用Array.SetValue赋值
arr.SetValue(123,1, 1);
//转换
int[,] arr2= (int[,])arr;
//用CLR的Set方法
arr2[1,2] =456;
多维数组的GetType也值得说明一下:
Array arr1= Array.CreateInstance(typeof(int),new int[] { 2,3 }, new int[] {1, 1 });
int[,] arr2= newint[2,3];
Console.WriteLine(arr1.GetType());
Console.WriteLine(arr2.GetType());
//都是System.Int32[,]
既然不是“以0开始的一维数组”,为什么GetType里没有*?这个在一维数组中,有*和没*可以区分是不是以0开始因为没*的一维数组CLR会特殊对待的。而多维数组永远不属于特殊对待那种的,所以要加*的话,多维数组永远会有*的,那么没有意义,不如把*去掉,看起来更简洁(有中间的逗号代表它是多维数组就够了)。
返回目录
http://blog.csdn.net/meifage9/article/details/6650253
其他关于数组的文章:
C# 数组趣事
http://www.cnblogs.com/mgen/archive/2011/07/31/2123218.html
.NET: Array Types in .NET
http://msdn.microsoft.com/en-us/magazine/cc301755.aspx
http://www.cnblogs.com/mgen/archive/2011/08/02/2125643.html