C#中数组的神奇之处是什么?

时间:2010-12-20 12:35:43

标签: c# arrays clr runtime

int[] a = new int[5];
string[] b = new string[1];

ab的类型都继承自抽象System.Array,但内置库中没有真正的类(似乎有一些运行时类型,你找不到int[]的类型定义类。你能告诉我编译时会发生什么吗?为什么他们(c#团队)制作这个设计(我的意思是为什么它不像Array<T>,而是他们使用带编译器魔法的抽象类)?

8 个答案:

答案 0 :(得分:18)

试图在.NET类型系统中解释这一点并不会让你走得太远。 JIT编译器和CLR内置了核心支持来处理创建数组。这样的陈述:

        var arr = new int[5];

生成此IL:

  IL_0001:  ldc.i4.5
  IL_0002:  newarr     [mscorlib]System.Int32

然后JIT编译器将其转换为此机器代码:

00000035  mov         edx,5                 ; arg2 = array size
0000003a  mov         ecx,6F535F06h         ; arg1 = typeof(int)
0000003f  call        FFD52128              ; call JIT_NewArr1(type, size)

这里的核心成分是专用的IL操作码 newarr ,而不是通常的 newobj 操作码,它创建了一个类的实例。并简单地转换为实际获取创建对象的CLR帮助函数。您可以使用SSCLI20源代码clr\src\vm\jithelpers.cpp查看此辅助函数。这里发布的内容太大了,但它经过了大量优化,可以尽可能快地运行这种代码,可以直接访问CLR代码可用的类型内部。

有两个这样的助手可用,JIT_NewArr1()创建一维(向量)数组,JIT_NewMDArr()创建多维数组。比较Type.MakeArrayType()可用的两个重载。

答案 1 :(得分:9)

  

他们为什么(c#团队)制作   这个设计(我的意思是为什么它不是   像Array这样的东西......

泛型是定义容器的理想选择,因为它们约束了元素类型,因此您无法插入类型A并尝试检索类型B.

但是直到CLR2 / C#2才添加泛型。因此,阵列必须以自己的方式提供类型安全。

即便如此,它与泛型并没有什么不同。您注意到int[]没有特殊课程。但也不会有Array<int>。在泛型中,只有泛型类Array<T>,而CLR“神奇地”为您使用的不同类型参数创建专用版本。因此,如果使用仿制药,那将毫不逊色。

尽管如此,在CLR中,任何对象的类型都会被定义(它存在为您可以操作的值),类型为Type,并且可以使用typeof获得。所以虽然没有任何数组类型的代码声明(为什么你需要看到它?),你可以查询一个Type对象。

顺便说一句,数组约束元素类型的方式存在设计缺陷。您可以声明一个数组:

int[] ints = ...

然后,您可以将其存储在更宽松的变量中:

object[] objs = ints;

但这意味着你可以插入一个字符串(至少它在编译时出现):

objs[3] = "Oh dear";

在运行时它会抛出异常。静态类型检查的想法是在编译时捕获这种东西,而不是运行时。泛型不会遇到此问题,因为它们不会根据类型参数的兼容性为泛型类实例赋予赋值兼容性。 (自从C#4 / CLR4以来,他们已经获得了在有意义的地方做到这一点的能力,但这对于可变数组来说是没有意义的。)

答案 2 :(得分:7)

查看Array课程。

使用[]语法声明数组时,编译器会在幕后使用此类。

对于C#,[]成为继承自System.Array

的类型

来自C#4.0规范:

  

§12.1.1System.Array类型

     

System.Array类型是所有数组类型的抽象基类型。从任何数组类型到System.Array都存在隐式引用转换(第6.1.6节),并且从System.Array到任何数组类型都存在显式引用转换(第6.2.4节)。请注意,System.Array本身不是数组类型。相反,它是一个类类型,从中派生所有数组类型。

答案 3 :(得分:2)

有这样的课。您不能继承它,但是当您编写“int []”时,编译器会创建一个继承System.Array的类型。因此,如果您声明一个变量:

int[] x;

此变量将具有继承System.Array的类型,因此具有其所有方法和属性。

这也与代表类似。定义委托时:

delegate void Foo(int x);
delegate int Bar(double x);

然后类型Foo实际上是一个继承System.MulticastDelegate的类,而Bar是一个继承System.Delegate的类。

答案 4 :(得分:2)

如果您想了解低级细节,我建议您获取ECMA 335规范并查找阵列:http://www.ecma-international.org/publications/standards/Ecma-335.htm

答案 5 :(得分:1)

我开始挖掘ECMA 335 spec,所以我想我会分享我所读的内容。

  

确切的数组类型是VES在需要时自动创建的。因此,   数组类型的操作由CTS定义。这些通常是:分配数组   基于大小和下限信息,索引数组以读取和写入值,   计算数组元素的地址(托管指针),并查询排名,   bounds,以及存储在数组中的值的总数。

     

VES为每个创建一个数组类型   可区分的阵列类型。

     

向量是System.Array的子​​类型,是由CLI预定义的抽象类。它提供了几个   可以应用于所有向量的方法。见分区IV。

     

虽然向量(§II.14.1)通过CIL指令直接支持,但所有其他数组都受支持   VES通过创建抽象类System.Array的子​​类型(参见Partition IV)

     

虽然向量(§II.14.1)通过CIL指令直接支持,但所有其他数组都受支持   VES通过创建抽象类System.Array的子​​类型(参见Partition IV)

     

VES为数组创建的类包含几个提供实现的方法   由VES:

它继续陈述,提供的方法非常详细:

  1. 两个构造函数
  2. 获取
  3. 地址(返回托管指针)
  4. VES表示虚拟执行系统the CLR is an implementation of it

    规范还详细说明了如何存储数组的数据(以行 - 主顺序连续),数组中允​​许的索引(仅基于0),创建向量时(单维,基于0的数组) )而不是使用不同的数组类型,当使用CIL指令newarr而不是newobj时(创建一个基于0的单维数组)。

    基本上编译器必须做的一切都是为了构建方法查找表等。对于常规类型,它必须为数组做,但它们只是在编译器/ JIT中编写了一个更通用和稍微特殊的行为。

    他们为什么这样做?可能是因为数组是特殊的,广泛使用的,并且可以以优化的方式存储。 C#团队并不一定做出这个决定。它更像是.NET的东西,它是Mono和Portable.NET的堂兄,所有这些都是CIL的东西。

答案 6 :(得分:0)

阵列对CLR来说很特殊。它们被分配了'newarr'指令,并且使用'ldelem *'和'stelem *'指令访问元素,而不是通过System.Array方法;

请参阅http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.newarr.aspx

您可以查看ildasm输出以查看数组的表示方式。

所以,回答你的问题 - 没有为任何特定数组生成新的类型声明。

答案 7 :(得分:0)

[]是用于在c#中定义数组的语法(合成糖)。也许CreateInstance将在运行时替换

 Array a = Array.CreateInstance(typeof(int), 5); 

相同
int[] a = new int[5];

CreateInstance的来源(取自反射器)

public static unsafe Array CreateInstance(Type elementType, int length)
{
    if (elementType == null)
    {
        throw new ArgumentNullException("elementType");
    }
    RuntimeType underlyingSystemType = elementType.UnderlyingSystemType as RuntimeType;
    if (underlyingSystemType == null)
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeType"), "elementType");
    }
    if (length < 0)
    {
        throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }
    return InternalCreate((void*) underlyingSystemType.TypeHandle.Value, 1, &length, null);
}