数组自定义索引及一维数组特例

 

 

返回目录

1. 关于CLI标准

  文中会引用Common Language Infrastructure (CLI) 标准的内容,如果你想亲自看一下CLI标准的内容。可以在这里下载:http://www.ecma-international.org/publications/standards/Ecma-335.htm

 

 

返回目录

2. CLR数组 - 针对一切数组

首先,从最上面的层次看,编译器和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

 

 

返回目录

3. CLR数组 - 一种特例

CLR中有一种数组是被特殊照顾的,那就是以0开始的一维数组。CLI标准文中以“Vector”称这种数组,在其他地方也常常被简称为“SZ Array”(single-dimensioned, zero-based array)。

这种数组除了包含有上述其他CLR数组类型具备的特点,还有如下被特殊赋予的性质:

那就是CIL(MSIL)语言的直接支持:CIL中的newarr, ldelem, stelem, 和ldelema指令都是只针对以0开始的一维数组!

 

原因很简单,这种数组是最常见的数组,因此CIL定义一些指令专门针对此类数组。

 

 

返回目录

4. C#以0开始的一维数组的IL

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;

 

 

 

返回目录

5. C#其他一维数组的IL

那么这里就是说不以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;

 

 

返回目录

6. C#多维数组的IL

这里就拿二维数组做示例。

来看下面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会特殊对待的。而多维数组永远不属于特殊对待那种的,所以要加*的话,多维数组永远会有*的,那么没有意义,不如把*去掉,看起来更简洁(有中间的逗号代表它是多维数组就够了)。

 

 

返回目录

7. 其他文章

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值