.NET 类型(Types)的那些事
引言
您是.Net工程师?那 .NetFramework中的类型您知道有三大类吗?(除了引用类型和值类型,还有?)
引用类型一定在“堆”上,值类型一定在“栈”上?
那引用类型在内存中的布局细节您又知道多少了?
.Net Framework 中的Types分类

C# type categorization. 带阴影的都是 C# 的内建类型关键字.
除了object and string(分别为System.Object 和System.String别名), 其他带阴影的都是简单的值类型.
下面是摘自《C#语言规范5.0》 –> 4.类型(page:77)
C# 语言的类型划分为两大类:值类型 (Value type) 和引用类型 (reference type)。
第三种类型是指针,只能用在不安全代码中。
引用类型和值类型在内存中如何分配的呢?
这一块我们将通过一小段代码来讲解,在讲解前让我们回顾下
引用类型 和值类型的赋值过程中在内存处理上的区别:
- 把一个值类型a(定义如下int a=80;)赋给另外一个值类型b(int b;),即(b=a;)时,会把a的值拷贝一份给a,如下图;
- 把一个引用类型a(定义如下Employee a=new employee();)赋给另外一个引用类型b(Employee b;),即(b=a;)时,会把a的地址(引用)拷贝一份给a,即他们指向同一个地址;
开始代码讲解,首先看代码如下:
Form myForm = new Form();
Size s = new Size (100, 100); // struct = value type
Font f = new Font (“Arial”,10); // class = reference type
myForm.Size = s;
myForm.Font = f;
注意代码中 myForm.Size中的Size和myForm.Font的Font是Form类型的属性(Property)不是类型(class type,代表某个类型)。
在.NetFramework中这样的使用方式极其普遍,初学者不要混淆了这两者。
讲解代码前给大家再提下知识点:
- Size是Struct类型,当然就是值类型(ValueType)
- Font是Class类型,当然就是引用类型(ReferenceType)
上面这段代码的内存中的分配,示意图:

很清楚地看到
- Size类型的s分配到了Stack上,而Front类型的f 和Form类型的myForm则分配在堆上。
- 并且myForm的Font属性引用到了Font类型f。
- myForm的Size属性有它自己的值(Width和Height),它是Size类型s的一个拷贝。
这里我们更清晰的看到了值类型和引用类型在值赋值过程中的区别
我们可以通过修改Font类型f的值,来修改myForm中的字体样式,但不能通过修改s来修改myFrom的Size。
引用类型的Object内存布局基础结构

上面这个图展示的结构是通过分析源码得出的:
- 首先ObjectHeader(在其所在的AppDomain中的那个Thread通过调用Monitor.Enter锁了这个对象)
- 接下来是Method Table 指针(该指针指向AppDomain中声明(定义)托管类型),如果程序集被加载到AppDomain neutral 中,那么所有的AppDomain中该类型实例的Method Table指针都一样。CLR 类型系统的该基础构建块在托管代码中都是可视的。(TypeHandle.Value 是一个IntPtr)
- 最后就是这部分就是该类型实例的值。
CLR object的这个实例对象的地址在垃圾回收时也有可能会发生变动。(具体参看GC中压缩过程)
\sscli20\clr\src\vm\object.h
//
// The generational GC requires that every object be at least 12 bytes
// in size.
#define MIN_OBJECT_SIZE (2*sizeof(BYTE*) + sizeof(ObjHeader))
A .NET object has basically this layout:
class Object
{
protected:
MethodTable* m_pMethTab; };
class ObjHeader
{
private:
// !!! Notice: m_SyncBlockValue *MUST* be the last field in ObjHeader.
DWORD m_SyncBlockValue; // the Index and the Bits
};
| Platform | 最小实例大小(bytes) |
| x86 | 12 bytes = 2*4+4 |
| x64 | 24 bytes = 2*8+8 |
引用类型的Object内存布局代表性结构
普通对象

数组对象 - Array

字符串对象

Boxing,小心您的值类型不经意间被装箱
int a=1;
object b=a;
这段代码大家都知道会发生装箱,装箱后原来的值类型会有哪些变化?看下装箱和拆箱的步骤:
装箱:
对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
第二步:将值类型的实例字段拷贝到新分配的内存中。
第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
有人这样理解:如果将Int32装箱,返回的地址,指向的就是一个Int32。我认为也不是不能这样理解,但这确实又有问题,一来它不全面,二来指向Int32并没说出它的实质(在托管堆中)。
拆箱:
检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。
有书上讲,拆箱只是获取引用对象中指向值类型部分的指针,而内容拷贝则是赋值语句之触发。我觉得这并不要紧。最关键的是检查对象实例的本质,拆箱和装箱的类型必需匹配,这一点上,在IL层上,看不出原理何在,我的猜测,或许是调用了类似GetType之类的方法来取出类型进行匹配(因为需要严格匹配)。
那么给你个自定义结构体,你还清楚什么情况会被装箱吗?
附加
为方便大家查看源码,这里提供一个源码索引表
SSCLI文件索引
| Item | SSCLI Path |
|---|---|
| AppDomain | \sscli\clr\src\vm\appdomain.hpp |
| AppDomainStringLiteralMap | \sscli\clr\src\vm\stringliteralmap.h |
| BaseDomain | \sscli\clr\src\vm\appdomain.hpp |
| ClassLoader | \sscli\clr\src\vm\clsload.hpp |
| EEClass | \sscli\clr\src\vm\class.h |
| FieldDescs | \sscli\clr\src\vm\field.h |
| GCHeap | \sscli\clr\src\vm\gc.h |
| GlobalStringLiteralMap | \sscli\clr\src\vm\stringliteralmap.h |
| HandleTable | \sscli\clr\src\vm\handletable.h |
| InterfaceVTableMapMgr | \sscli\clr\src\vm\appdomain.hpp |
| Large Object Heap | \sscli\clr\src\vm\gc.h |
| LayoutKind | \sscli\clr\src\bcl\system\runtime\interopservices\layoutkind.cs |
| LoaderHeaps | \sscli\clr\src\inc\utilcode.h |
| MethodDescs | \sscli\clr\src\vm\method.hpp |
| MethodTables | \sscli\clr\src\vm\class.h |
| OBJECTREF | \sscli\clr\src\vm\typehandle.h |
| SecurityContext | \sscli\clr\src\vm\security.h |
| SecurityDescriptor | \sscli\clr\src\vm\security.h |
| SharedDomain | \sscli\clr\src\vm\appdomain.hpp |
| StructLayoutAttribute | \sscli\clr\src\bcl\system\runtime\interopservices\attributes.cs |
| SyncTableEntry | \sscli\clr\src\vm\syncblk.h |
| System namespace | \sscli\clr\src\bcl\system |
| SystemDomain | \sscli\clr\src\vm\appdomain.hpp |
| TypeHandle | \sscli\clr\src\vm\typehandle.h |
更多源码参考http://www.projky.com/dotnet
参考
The Truth About .NET Objects And Sharing Them Between AppDomains
Six important .NET concepts: Stack, heap, value types, reference types, boxing, and unboxing
Shared Source Common Language Infrastructure
[翻译经典文章]深入.NET Framework内部, 看看CLR如何创建运行时对象的
.NET 类型(Types)的那些事的更多相关文章
- JavaScript 类型判断的那些事
先准备几个变量 var a = "abcde."; var b = 222; var c= [1,2,3]; // 或者 new Array() var d = new Date( ...
- Swift语法3.03(类型Types)
类型 在Swift中,有两种类型:命名型类型和复合型类型.命名型类型是在定义时可以给定的特定名字的类型.命名型类型包括类,结构体,枚举和协议.例如,自定义的类MyClass的实例拥有类型MyClass ...
- 关于Mach-O类型文件那点事
Mach-O文件简介 Mach-O是一种文件格式,是Mach Object文件格式的缩写. 它通常应用于可执行文件,目标代码,动态库,内核转储等中. Mach-O作为大部分基于Mach核心的操 ...
- TYPES、DATA、TYPE、LIKE、CONSTANTS、STATICS、TABLES
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- Python中为什么推荐使用isinstance来进行类型判断?而不是type
转自:http://www.xinxingzhao.com/blog/2016/05/23/python-type-vs-isinstance.html Python在定义变量的时候不用指明具体的的类 ...
- 重装eclipse要做的事
当我们要在新环境上安装eclipse时,往往会做很多的个性修改和安装一些插件,下面就这些做一下总结: 一.插件 1.svn插件(subclipse) 插件官网下载地址:http://subclipse ...
- item 6: 当auto推导出一个不想要的类型时,使用显式类型初始化的语法
本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 Item 5解释了比起显式指定类型,使用auto来 ...
- 【Python】Python—判断变量的基本类型
type() >>> type(123)==type(456) True >>> type(123)==int True >>> type('ab ...
- C++中的void类型
Technorati 标签: void,指针 1.1. void类型 void类型其实是一种用于语法性的类型,而不是数据类型,主要用于作为函数的参数或返回值,或者定义void指针,表示一种未知类型. ...
随机推荐
- 高效率去掉js数组中重复项
Array类型并没有提供去重复的方法,如果要把数组的重复元素干掉,那得自己想办法: function unique(arr) { var result = [], isRepeated; for (v ...
- 关于WebBrowser访问百度地图
前段时间遇到一个困惑用WebBrowser访问百度地图的时候,百度会自动转至让下载sdk的页面,经过一个仁兄的点拨,可以改变WebBrowser的agent来骗过网站.经过试验成功.贴源码如下: st ...
- 从零开始学 Java - Windows 下安装 Tomcat
谁都想分一杯羹 没有一个人是真正的无私到伟大的,我们试着说着做自己,与人为善,世界和平!殊不知,他们的真实目的当你知道后,你会被恶心到直摇头并下意识地迅速跑开,下辈子都不想见到他.不过,他没错,你也没 ...
- 企业管理咨询Interview Checklist
企业管理咨询Interview Checklist 一. 企业战略 1. 您对公司所处行业的看法如何? 2. 请您介绍一下公司的发展历程,主要业务开展状况及核心竞争力.关键成功因素有哪些? 3. 在您 ...
- UML常用图
序列图 活动图
- javascript组合继承
javascript继承有几种继承方式,现在来说说其中的组合继承. 组合继承是结合了原型链和借用构造函数这两种技术的继承方式,分别利用它们的长处,避免了短处.那就先说说这两种技术吧. 原型链 原型链 ...
- (转)SQL 优化原则
一.问题的提出 在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用 系统提交实际应用后,随着数据库中数据的增加,系 ...
- 向ES6靠齐的Class.js
写在前面 在2008年的时候,John Resig写了一 Class.js,使用的方式如下: var Person = Class.extend({ init: function(isDancing) ...
- 纯HTML5+CSS3制作生日蛋糕
以一个前端开发的身份绘制一个简单的蛋糕庆祝一下今天这个好日子吧,程序员庆生的乐趣与哀愁啊.写的比较简陋,感兴趣的看一下吧. 先发个效果图吧 蛋糕分为三个部分,底部蛋糕,顶层蛋糕和蜡烛部分.HTML的布 ...
- SharePoint 2013 定制搜索显示模板(二)
前言 之前一篇博客,简单的介绍了如何定制搜索显示模板,这一次,我们介绍一下如何定制搜索显示时,弹出来的那个页面,相信这个大家也都会遇到的. 1.第一部分就是搜索显示模板的部分,第二部分就是搜索项目详情 ...

