【问题标题】:What's the magic of arrays in C#C#中数组的魔力是什么
【发布时间】:2010-12-20 12:35:42
【问题描述】:
int[] a = new int[5];
string[] b = new string[1];

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

【问题讨论】:

  • 一个有趣的问题,但我想 CLR 的神奇贡献不仅仅是 C# 编译器的。
  • “没有这样一个真正的班级”是什么意思?
  • @BoltClock: 你找不到这个类的源代码吧?我的意思是真正的类型,而不是System.Array。对不起我的英语不好。

标签: c# arrays clr runtime


【解决方案1】:

试图在 .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() 可用的两个重载。

【讨论】:

  • 是的,但是即使在 C# 中类被称为 Array<T> 而不是 T[] ,编译器也没有理由仍然不能使用 newarr IL 操作码。当然,真正的答案是 C# 1.0 没有泛型,但它肯定需要数组。
【解决方案2】:

他们(c# 团队)为什么要制作 这个设计(我的意思是为什么它不是 类似数组...

泛型是定义容器的理想选择,因为它们限制元素类型,因此您不能插入类型 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 开始,他们已经获得了在有意义的地方执行此操作的能力,但这对于可变数组来说没有意义。)

【讨论】:

  • +1。令人惊讶的是,其他答案都没有提到真正的答案,即 C# 1.0 没有泛型。
【解决方案3】:

查看Array 类。

当使用[] 语法声明一个数组时,编译器会在后台为你使用这个类。

对于 C#,[] 成为继承自 System.Array 的类型。

来自 C# 4.0 规范:

§12.1.1 System.Array 类型

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

【讨论】:

  • 我猜 OP 要问的是如何从Array 创建对象,因为它是一个抽象类。
  • 不,这不是使用Array的语法糖;没有“不加糖”的语法。
【解决方案4】:

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

int[] x;

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

这也类似于委托。定义委托时:

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

那么Foo类型其实就是继承System.MulticastDelegate的类,Bar是继承System.Delegate的类。

【讨论】:

  • 我知道。他们是怎么做到的?
  • 哦..我现在明白了。他们可以这样做,因为允许编译器和 CLR 做您作为程序员不允许做的事情。例如,c# 编译器不允许您继承 System.Delegate,但编译器本身没有问题。它也可以让你这样做,但 c# 语言设计者决定它不会。以类似的方式,CLR 可以生成继承 System.Array 的类型,但它不允许您作为程序员这样做。
  • @Timwi 你能证明这个说法是正确的吗?编译器/JIT 正在有效地创建 System.Array 的子类型,而 Oded 等其他海报也指出了这一点。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    我浏览了ECMA 335 spec,所以我想我会分享我读到的内容。

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

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

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

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

    虽然向量(§II.14.1)通过 CIL 指令直接支持,但所有其他数组都由 VES 通过创建抽象类 System.Array 的子类型(参见第 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 的东西。

    【讨论】:

      【解决方案7】:

      数组对于 CLR 来说是特殊的。它们使用“newarr”指令分配,元素使用“ldelem*”和“stelem*”指令访问,而不是通过 System.Array 方法;

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

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

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

      【讨论】:

        【解决方案8】:

        [] 是用于在 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);
        }
        

        【讨论】:

        • 不一样。第一个编译成call 指令到Array.CreateInstance,第二个编译成newarr 指令。另外,局部变量的类型不同。
        • @Timwi,我敢打赌 Array.CreateInstance 会调用 newarr
        • @MillieSmith:当然,在某个地方,将使用newarr 指令。但是这两个sn-ps的代码不相同(不等价)。
        猜你喜欢
        • 2023-03-29
        • 2016-09-23
        • 2015-09-09
        • 2012-05-15
        • 2012-11-30
        • 2011-04-02
        • 1970-01-01
        • 1970-01-01
        • 2010-09-24
        相关资源
        最近更新 更多