我的大脑今天早上有一个段错误,试图准确理解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#编译器使用流量分析来计算正在发生的事情并防止任何非法操作? CLR在被翻译成IL时真的不关心Foo的类型声明吗?
道歉,如果这是一个冗长且措词不清的问题 - 请告诉我是否需要澄清!
TL; DR - CLR中的多态引用如何工作?如何保持实际与声明的类类型之间的差异,并且您能告诉最终的声明来自结果IL吗?
答案 0 :(得分:5)
你的想法太复杂了。
引用'f'本身是否包含类型信息?
没有。它不必。它只是前面构造的Bar
对象内存开头的地址。 该对象包含一个虚方法表(可能还有对其关联的Type
对象 1 的引用,但这与此无关。)
当我打电话给f.Baz()时,我是否认为通过Bar的MethodTable进行调度是正确的?
是
C#编译器是否使用流量分析来计算出现的情况并防止任何非法操作?
流量分析很复杂,完全没必要。编译器准确允许f
的声明类型允许的那些操作 - Foo
。编译器根本不会关注关于f
的实际(=动态)类型。
你能告诉最终的声明来自最终的IL吗?
取决于。 对象不具有静态类型,因此“在运行时告诉其静态类型”是没有意义的。另一方面,声明有所不同。如果变量是方法的形式参数,那么您可以(在运行时)使用反射来确定方法参数的声明类型。
对于局部变量,此操作再次毫无意义。另一方面,IL 通过.locals
存储此信息(作为元数据?),因此代码 理论上可以进行逆向工程(Cecil和Reflector执行此操作) )获取静态类型的变量。
1 我在这里猜测,但这实际上是不可能的。如果每个对象都拥有自己对相关Type
对象的引用,那么这将意味着指针的额外开销。此外,此引用完全没有必要,因为对象可以简单地调用GetType
来获取其类型。 GetType
只需要为每个类实现一次(实际上是一种静态方法),并通过通常的虚函数表调度。因此,每个类只需要一个对Type
的引用,而不是每个对象。
答案 1 :(得分:1)
C#有没有办法从实际的对象图中知道'f'是Foo
编译器静态知道
引用'f'本身是否包含类型信息?
有趣的是,它必须以某种方式包含在IL中。
当我打电话给f.Baz()时,我是否正确地认为发送是通过Bar的MethodTable进行的?
是。这是通过f指向的实例找到的。