【问题标题】:What exactly is a CLR reference, and how does it hold type information?究竟什么是 CLR 引用,它如何保存类型信息?
【发布时间】:2010-11-08 12:43:25
【问题描述】:

今天早上我的大脑出现了段错误,试图准确了解 C# 如何以及何时可以从对该对象的引用中计算出对象的类型。考虑以下高度非原创的示例代码:

class Foo { public virtual void Baz() { } }
class Bar : Foo { }

class Program {
    static void Main() {
        Foo f = new Bar();
        f.Baz();
    }
}

那里的引用类型是Foo,但实际创建的对象实例是Bar。该 Bar 实例有一些开销,即同步块索引和对 MethodTable 的引用,大概是 Bar 的 MethodTable。如果您查看堆上的 Bar 对象,唯一的类型线索就是 MethodTable 引用,这表明它是 Bar。

那么问题来了。 C# 有什么方法可以从实际的对象图中知道“f”是 Foo,如果是,怎么做?引用“f”本身是否包含类型信息?当我调用 f.Baz() 时,我是否认为调度是通过 Bar 的 MethodTable 发生的?仅仅是C#编译器使用流分析来弄清楚发生了什么并防止任何非法操作的情况吗?当 Foo 被翻译成 IL 时,CLR 是否真的不关心 Foo 的类型声明?

抱歉,如果这是一个冗长且措辞不佳的问题 - 如果需要任何澄清,请告诉我!

TL;DR - CLR 中的多态引用如何工作?实际与声明的类类型之间的差异是如何持续存在的,您能从结果 IL 中分辨出原始声明是什么吗?

【问题讨论】:

    标签: c# clr


    【解决方案1】:

    你想得太复杂了。

    引用“f”本身是否包含类型信息?

    没有。它不必。它只是前面构造的Bar 对象内存的开头地址。 那个对象包含一个虚拟方法表(并且可能是对其关联的Type对象的引用1,但这在这里无关紧要)。

    当我调用 f.Baz() 时,我是否认为调度是通过 Bar 的 MethodTable 发生的?

    是的。

    难道只是C#编译器使用流分析来判断发生了什么并防止任何非法操作的情况吗?

    这里的流量分析很复杂,完全没有必要。编译器完全允许 ​​f 的声明类型允许的那些操作,即 Foo。编译器根本不关心 f 的实际(= 动态)类型。

    您能说出最终的 IL 的原始声明是什么吗?

    视情况而定。 对象没有静态类型,所以“在运行时告诉它的静态类型”是没有意义的。另一方面,声明不同。如果变量是方法的形式参数,那么可以肯定,您可以(在运行时)使用反射来确定方法参数的声明类型。

    对于局部变量,再一次这个操作是没有意义的。另一方面,IL 确实通过.locals 存储此信息(作为元数据?),因此代码理论上可以被逆向工程(Cecil 和 Reflector 这样做)以获取变量的静态类型。


    1 我在这里猜测,但这实际上是不可能。如果每个对象都拥有对关联Type 对象的引用,这将意味着额外的指针开销。此外,这个引用是完全没有必要的,因为对象可以简单地调用自己的GetType 来获取它的类型。 GetType 只需要为每个类实现一次(实际上是一种静态方法),并通过通常的虚函数表进行调度。因此,每个类只需要一个Type 的引用,而不是每个对象。

    【讨论】:

    • 这真的很有帮助,谢谢。您能否更详细地解释一下“对象没有静态类型”的注释?我认为堆上给定的具体对象具有明确的静态类型,由 MethodTable 引用指向的任何内容确定?听起来一般的答案是声明的类型只对编译器很重要,而 CLR 只知道编译器是否在 IL 中留下了某种元数据标记。听起来对吗?
    • @Jon:这就是它的 dynamic 类型。静态类型只分配给 declarations,即代码中的标识符。由于两个(不同的静态类型)变量可以指向同一个对象,所以一个对象不能具有静态类型(诚然,这过于简单了)。
    • 哦,我没有意识到这一点,尽管现在你提到它是完全有道理的。我现在怀疑我试图找出的更好的措辞可能是:“CLR 中的引用是否包含静态类型信息”。
    【解决方案2】:

    C# 有什么方法可以从实际的对象图中知道 'f' 是一个 Foo

    编译器静态知道的

    引用“f”本身是否包含类型信息?

    有趣,它必须以某种方式包含在 IL 中。

    当我调用 f.Baz() 时,我是否认为调度是通过 Bar 的 MethodTable 发生的?

    是的。这是通过 f 指向的实例找到的。

    【讨论】:

    • 感谢您的回答 - “有趣,它必须以某种方式包含在 IL 中。”部分确实很有趣。你知道这是怎么发生的吗?它会在我的示例代码中发生吗,即在 f.Baz() 的调用站点,我可以从 IL 或通过检查对象图来判断 f 实际上被声明为 Foo 吗?
    • 你不能在不知道它是 Foo 的情况下拥有 'f'... 引用很像一个值类型,你怎么能在不知道它是 int 的情况下拥有 'i'?
    猜你喜欢
    • 1970-01-01
    • 2017-03-01
    • 2014-06-03
    • 2011-05-03
    • 2011-12-28
    • 2017-06-10
    相关资源
    最近更新 更多