当你调用GetType()方法时,实际上发生了什么?

6

众所周知,C#对象具有指向其类型的指针,因此当您调用GetType()时,它会检查该指针并返回对象的实际类型。但如果我这样做:

A objA = new A();
object obj = (object)objA;
if (obj.GetType() == typeof(object)) ; // this is true

但是,在 object obj = (object)objA; 这里会发生什么呢?它会创建某种引用对象引用 objA,但具有指向 object 的类型指针,还是它是一个全新的对象,只是恰好指向与 objA 相同的属性、字段等。当然,现在可以访问两个对象,并且它们将具有不同的类型,但指向相同的数据。这是如何工作的?
另一个问题是:GetType()是否保证返回对象的实际类型?例如,假设有一个签名为 void Method(object sender) 的方法,我们将 A 类型的对象作为参数传递。那么,sender.GetType() 会返回类型 A 还是 object?为什么呢?
另一个棘手的问题是,你可以执行 (A)obj,它会起作用。CLR 如何知道 obj 曾经是类型 A
如果有人能比 "C# via CLR" 更清晰地解释一下,那就太好了。
更新。我错了,在发布问题之前应该先运行代码。所以,如果 GetType() 确实总是返回实际类型,那么所有其他问题也变得清晰明了。

11
你的问题基于完全错误的前提。标记为“这是真的”的那一行并不是真的。如果你不相信我,请尝试编写一个实际可编译和运行的小程序。GetType()总是返回对象的实际运行时类型。你所有的问题都基于错误的假设,即编译时类型与此有关系,但实际上并没有关系。 - Eric Lippert
@Eric,你说得对,这正是我一直以为的,只是在一个项目中有些代码表现不同,分散了我的注意力,我的错。 - Egor Pavlikhin
1个回答

25

众所周知,C#对象中含有指向它们类型的指针。因此,当你调用GetType()方法时,它会检查该指针并返回一个对象的真实类型。

正确。

假如我这样写:

class A {}
class P
{
    public static void Main()
    {
        A objA = new A(); 
        object obj = (object)objA; 
        bool b = obj.GetType() == typeof(object) ; // this is true
    }
}

不,那是错误的。试试看!

但是,当执行 object obj = (object)objA; 时会发生什么?

objA 中的引用被复制到变量 obj 中。 (除非 A 是值类型,在这种情况下,它会被装箱并将对盒子的引用复制到 obj。)

它会创建某种引用对象,该对象引用 objA,但具有指向 object 的类型指针,还是一个全新的对象,只是恰好指向与 objA 相同的属性、字段等。

都不是。它只是复制引用,完全没有改变。

当然,现在您可以访问这两个对象,并且它们将具有不同的类型,但指向相同的数据。 这怎么工作的?

它不起作用。这个问题的前提是错误的假设。它们不会有不同的类型。它们是相同的引用。 变量的类型不相关;您没有询问变量的类型,而是询问变量内容的类型。

GetType() 是否保证返回对象的实际类型?

对于您的目的,是的。 有涉及到 COM 互操作的晦涩情况,它不会。

例如,假设有一个签名为 void Method(object sender) 的方法,并且我们将类型为 A 的对象作为参数传递。sender.GetType() 是否会返回类型 A 还是 object?

是类型 A。

原因是什么?

因为这是对象的类型。

另一个棘手的问题是,您可以执行 (A)obj,它会起作用。 CLR 如何知道 obj 曾经是类型 A?

C# 编译器会生成 castclass 指令。 castclass 指令会进行运行时检查,以验证对象引用是否实现了所需类型。如果没有,则 CLR 抛出异常。


Eric,出于兴趣,我如何区分变量的运行时类型和它所引用对象的类型?类型系统不是有两个级别/阶段吗?即编译时类型(用于生成此类错误:((object)mystring).Substring()),以及运行时类型(GetType())用于反射。当编译失败时,这是因为编译时类型检查(即根本不是运行时类型检查,因此不使用任何变量或实例类型)。变量自身的类型(而不是它所引用的事物的类型)只在编译时显现吗? - Andrew Matthews
抱歉,当我说“区分”时,我应该说“通过反射在运行时区分”。 - Andrew Matthews
1
@Andrew:变量的类型在 C# 编译器和 CLR 验证器中分别在编译时和运行时确定。验证器通过确保没有变量被赋予不兼容其类型的值来验证代码的可验证类型安全性。如果你的问题是“我能使用反射反映本地变量的类型吗”,答案是否定的,因为本地变量不会通过反射暴露出来。当然,你可以反射一个字段或形式参数的类型。 - Eric Lippert
2
本地变量通过反射公开,但是没有任何方法将实际变量映射到LocalVariableInfo。 - Jb Evain
@Jb:您当然是正确的,我忘记了那个。但是,由于它们在元数据中没有按名称公开,因此它们并不是很有用。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接