.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指针,表示一种未知类型. ...
随机推荐
- STM32CubeMX安装指南
1.STM32CubeMX软件下载 地址:http://pan.baidu.com/s/1bn8sXOV 密码:6u3p 2.安装 1)安装Java SDK 2)安装SetupSTM3 ...
- 在DirectShow中支持DXVA 2.0(Supporting DXVA 2.0 in DirectShow)
这几天在做dxva2硬件加速,找不到什么资料,翻译了一下微软的两篇相关文档.并准备记录一下用ffmpeg实现dxva2,将在第三篇写到.这是第二篇.,英文原址:https://msdn.microso ...
- 定制自己的mybatis生成
MyBatis Generator原生提供的生成方式targetRuntime有几种,但都不符合项目需求或想自定义自己的方法. 网上的文章也很多: 如:http://generator.sturgeo ...
- ABP 初探 之 AbpSession 扩展
Abp的权限管理是基于 Identity,所有的扩展也是基于 claims .claims 有许多默认属性,具体连接 关于 Identity的详细介绍,可以参考园友博客 继承 Microsoft.As ...
- mariadb 10.2.3支持延时复制
在mysql 5.6开始就支持延时复制,这在一些需要维护大量非标准化系统或者运维技术水平较低的公司和开发人员众多的项目组这是一个非常有价值的特性,可以说误操作的概率跟一个城市车祸概率的水平差不多了,我 ...
- Netty(一)引题
本文介绍Java BIO(同步阻塞IO),伪异步IO,NIO(非阻塞IO),AIO(异步IO)这四种IO的情况,并对不同IO模型作比较. 目录 1.BIO 2.伪异步IO 3.NIO 4.AIO 5. ...
- PHP语法
* PHP语法 * 常量与变量 * 常量 - 一旦定义并初始化后,值不会改变 * 使用const关键字 const 常量名=常量值 * define(常量名,常量值) * 变量 - "$&q ...
- javascript中的一些核心知识点以及需要注意的地方
前言 近期杂事甚多,这些事情的积累对知识体系的提升有好处,但是却不能整理出来,也整理不出来 比如说我最近研究的Hybrid在线联调方案便过于依赖于业务,就算分享也不会有人读懂,若是抽一点来分享又意义不 ...
- js 理解new的运行机制
先上段代码: function People(name) { this.name = name; } People.prototype.sayName = function () { console. ...
- MFC--响应鼠标和键盘操作
一个程序最重要的部分之一是对鼠标和键盘操作的响应. 一. 理解鼠标事件.之前对鼠标事件的认识仅仅局限于处理控件的单击与双击事件.但实际鼠标的操作包含很多.这里将以一个画图的小程序讲解对鼠标的响应. ...

