Object.GetType()到底是怎么工作的

来源:互联网 发布:数控旋压机编程 编辑:程序博客网 时间:2024/05/22 06:43

Object.GetType()到底是怎么工作的

和这个问题相关的:一个object是怎么知道它是什么类型的?编译器或者运行时知道某个object的类型吗?这些和CLR有什么关系?C#是种静态类型和强类型的语言,所以可能GetType()方法并不是真的存在,只不过编译器在编译的时候直接把object的类型替换进去了?

对于最后一个问题,我们可以试试下面这个例子:

object o = new Random().Next(2) == 0 ? new BaseClass() : new DerivedClass();

o.GetType()会返回什么结果呢?在编译时或JIT时,我们并不能知道确切的答案。只有在代码被执行的时候,o的类型才会被确定。因此,看起来o的类型只有在运行时才会被确定。让我们再深究一些。

如果查看.NET Framework Reference Source的Object.GetType()方法,我们发现,并没有什么有价值的东西:

// Returns a Type object which represent this object instance.[MethodImplAttribute(MethodImplOptions.InternalCall)]public extern Type GetType();

这里需要注意一点,这个方法并没有被标记成virtual,但是却表现得像是virtual——因为每一个object都会返回它真实的类型。这主要是因为C#特殊的内部实现。InternalCall属性表示这个方法是由CLR内部实现的,我们查看CoreCLR的源代码,就能看出一些端倪。代码如下:

FCFuncStart(gObjectFuncs)     FCIntrinsic("GetType", ObjectNative::GetClass, CORINFO_INTRINSIC_Object_GetType)     FCFuncElement("MemberwiseClone", ObjectNative::Clone) FCFuncEnd()

我们再看看GetType()的一个实现(我移除了一些不相关的代码):

FCIMPL1(Object*, ObjectNative::GetClass, Object* pThis) {    // ...    OBJECTREF objRef = ObjectToOBJECTREF(pThis);    if (objRef != NULL)    {       MethodTable* pMT = objRef->GetMethodTable();       OBJECTREF typePtr = pMT->GetManagedClassObjectIfExists();       if (typePtr != NULL)       {          return OBJECTREFToObject(typePtr);       }    }    else       FCThrow(kNullReferenceException);    FC_INNER_RETURN(Object*, GetClassHelper(objRef)); } FCIMPLEND

简单来说,就是获取所谓的object的MethodTable(Object::GetMethodTable)并且返回对应的Type对象(MethodTable::GetManagedClassObjectIfExists);当找不到对应的Type对象时,就创建出来并返回(GetClassHelper)。为了更清楚地解释这个问题,我们得分几个步骤依次讨论。

MethodTable

提到CLR的类型系统,就不得不提到MethodTable,一种描述类型的数据结构。这个数据类型被保存在进程的一块独立内存空间中。它能够描述这个类型中包括什么方法、实现了什么接口。这里就不再展开讲MethodTable的具体结构了。这里,咱们只需要把MethodTable看成是类型的某种内部描述。看看在内存中某个class的对象(这些对象保存在堆上),我们可以看出,其中第一个字段就是一个指向MethodTable的指针:

// code:Object is the respesentation of an managed object on the GC heap.//// See code:#ObjectModel for some important subclasses of code:Object//// The only fields mandated by all objects are//// * a pointer to the code:MethodTable at offset 0// * a poiner to a code:ObjHeader at a negative offset. This is often zero. It holds information that// any addition information that we might need to attach to arbitrary objects.//class Object{   protected:      PTR_MethodTable m_pMethTab;      // ...

GetMethodTable()方法返回的就是这个指针:

PTR_MethodTable Object::GetMethodTable() const{   // ...   return m_pMethTab;   // ...}

堆上的每一个对象都有严格的内存布局——size取决于是32位机还是64位机:

object on heap

正如我们看到的,除了数据本身,还有额外的空间用于保存MethodTable指针。指针中保存的地址可能是内存中任何一个位置,从那个位置的对象再指向一连串的其他对象 。这个地址所指向的第一个对象中,通常什么都不会保存,只保存0,但是在一些同步机制中,某种程度上来说,它同样是有用的。不管怎么说,现在我们了解了,获取类型的第一步就是先获取MethodTable数据结构的首地址。

Type

为了获取代表MethodTable的Type对象,会执行OBJECTREF MethodTable::GetManagedClassObjectIfExists方法,来搜索内部结构,检查该Type对象是否已经被创建了。如果不存在,就会间接调用MethodTable::GetManagedClassObject方法来创建它。最终,我们会获取指向对应Type对象的指针。

至此,我们的目的达成啦!事实上,我们可以看出来,GetType()方法并不复杂,它只是解析了堆上每个对象都保存着的某个CLR结构体。但是,对于栈上的对象呢?继续往下看。

Object on the stack

栈上保存的所谓的值类型对象,我们称它们struct。需要强调的是,我们这样称呼它们,是基于它们的实现细节,而并非基于它们自身的特性。栈上的对象具有更简单的内存结构——只由它们的值组成:

object on stack

换言之,CLR不会在任何地方保存栈上对象的type,但是这不代表值类型对象的MethodTable不存在。它只是没有保存在对象内部,这是因为编译器已经知道了。正是因为值类型对象不存在继承,所以在编译的时候,对应的type已经确定了。那么问题来了,这种情况下,GetType()方法是怎么运作的呢。且看下面这个例子:

struct MyStruct { int x; int y; }void Main(){   MyStruct s = new MyStruct();   var t = s.GetType();}

编译器是怎么知道s是什么类型的呢?是编译器/JIT替换了对应的类型?有可能是这样,不过我们还是先看看上面的例子会生成什么CIL:

IL_0001: ldloca.s 00 // sIL_0003: initobj UserQuery.MyStructIL_0009: ldloc.0 // sIL_000A: box UserQuery.MyStructIL_000F: call System.Object.GetTypeIL_0014: stloc.1 // t

答案很明显。在调用GetType()方法之前,会先调用值类型对象的装箱操作(这时编译器已经知道了确切的类型)。装箱操作会在堆上创建一个新对象,正是这个对象中保存了对应的MethodTable指针。

HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData){   TypeHandle clsHnd(type);   MethodTable *pMT = clsHnd.AsMethodTable();   // ..   newobj = pMT->FastBox(&unboxedData);   return(OBJECTREFToObject(newobj));}HCIMPLENDOBJECTREF MethodTable::FastBox(void** data){   // ..   OBJECTREF ref = Allocate();   CopyValueClass(ref->UnBox(), *data, this, ref->GetAppDomain());   return ref;}

所以,我们又绕了回去。因为被装箱的对象有特定的内存结构,我们使用标准的Object.GetType()方法就能够从对象的MethodTable中取到对应的Type对象,并返回。

总结

以上就是GetType()如果工作的。有任何问题,欢迎提问!

P.S. 再回答这个问题的时候,又想到了一些其他的问题,以后有机会再慢慢说:

  1. 为什么CLR不支持结构体继承?
  2. 动态类型是怎么支持GetType()的?动态类型和我们今天所讲的有什么关联?
  3. 如果把类型声明成public new Type GetType() {return typeof(string);}会发生什么?
  4. MethodTable及其相关的结构到底是什么样的?它们是在何时何地创建的?
  5. 既然编译器知道值类型的确切type,为什么不直接在调用GetType()的时候把结果返回去?
  6. class C {}struct S {}(空对象)在内存中是什么样的?它们占用多少内存?
0 0
原创粉丝点击