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位机:
正如我们看到的,除了数据本身,还有额外的空间用于保存MethodTable指针。指针中保存的地址可能是内存中任何一个位置,从那个位置的对象再指向一连串的其他对象 。这个地址所指向的第一个对象中,通常什么都不会保存,只保存0,但是在一些同步机制中,某种程度上来说,它同样是有用的。不管怎么说,现在我们了解了,获取类型的第一步就是先获取MethodTable数据结构的首地址。
Type
为了获取代表MethodTable的Type对象,会执行OBJECTREF MethodTable::GetManagedClassObjectIfExists
方法,来搜索内部结构,检查该Type对象是否已经被创建了。如果不存在,就会间接调用MethodTable::GetManagedClassObject
方法来创建它。最终,我们会获取指向对应Type对象的指针。
至此,我们的目的达成啦!事实上,我们可以看出来,GetType()方法并不复杂,它只是解析了堆上每个对象都保存着的某个CLR结构体。但是,对于栈上的对象呢?继续往下看。
Object on the stack
栈上保存的所谓的值类型对象,我们称它们struct。需要强调的是,我们这样称呼它们,是基于它们的实现细节,而并非基于它们自身的特性。栈上的对象具有更简单的内存结构——只由它们的值组成:
换言之,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. 再回答这个问题的时候,又想到了一些其他的问题,以后有机会再慢慢说:
- 为什么CLR不支持结构体继承?
- 动态类型是怎么支持GetType()的?动态类型和我们今天所讲的有什么关联?
- 如果把类型声明成
public new Type GetType() {return typeof(string);}
会发生什么? - MethodTable及其相关的结构到底是什么样的?它们是在何时何地创建的?
- 既然编译器知道值类型的确切type,为什么不直接在调用GetType()的时候把结果返回去?
class C {}
和struct S {}
(空对象)在内存中是什么样的?它们占用多少内存?
- Object.GetType()到底是怎么工作的
- Spring @Transactional 到底是怎么工作的?
- 电到底是怎么工作的?
- spring的@Transactional到底是怎么工作的
- 3D扫描仪到底是怎么工作的?
- acegi,里面到底是怎么走的?
- 疑问:yield到底是怎么运作的?
- memory到底是怎么计算出来的
- Spring 到底是怎么跑起来的
- 这世界到底是怎么来的
- 到底是怎么传参的?
- SQL语句到底是怎么执行的?
- 程序员到底是怎么来的?
- 我们的工作到底是为了什么?
- 软件工程师的工作到底是啥样子?
- LayoutInflater到底是如何工作的
- Spring MVC 到底是如何工作的?
- Spring MVC 到底是如何工作的?
- Android——了解Paint常见属性
- JavaScript 自定义文本框光标——初级版
- 半双工与全双工
- Codeforces 369C Valera And Elections 树形DP
- IE下响应304
- Object.GetType()到底是怎么工作的
- CSS中样式命名tips
- django遇到TypeError: can't multiply sequence by non-int of type 'tuple'
- python之装饰器decorator
- java多线程学习二
- Spark Job调优(Part 1)
- HashMap Hashtable区别
- hadoop第三坑
- 00003 不思议迷宫.0009.4:攻防计算