我是靠谱客的博主 传统飞机,最近开发中收集的这篇文章主要介绍Object.GetType()到底是怎么工作的,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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 // s
IL_0003: initobj UserQuery.MyStruct

IL_0009: ldloc.0 // s
IL_000A: box UserQuery.MyStruct
IL_000F: call System.Object.GetType

IL_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));
}
HCIMPLEND

OBJECTREF 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 {}(空对象)在内存中是什么样的?它们占用多少内存?

最后

以上就是传统飞机为你收集整理的Object.GetType()到底是怎么工作的的全部内容,希望文章能够帮你解决Object.GetType()到底是怎么工作的所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部