.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指针,表示一种未知类型. ...
随机推荐
- Windows 10预览版14316开启Bash命令支持
00x0 前言 4月7日凌晨,微软推送了最新的Windows 10一周年更新预览版14316,其中重要的是原生支持Linux Bash命令行支持. 00x1 问题 如何开启Linux Bash命令行? ...
- 在Winform程序中设置管理员权限及为用户组添加写入权限
在我们一些Winform程序中,往往需要具有一些特殊的权限才能操作系统文件,我们可以设置运行程序具有管理员权限或者设置运行程序的目录具有写入的权限,如果是在操作系统里面,我们可以设置运行程序以管理员身 ...
- 执行后台任务的利器——Hangfire
今年1月31日,在微软的MVP 2015社区大讲堂上,我给大家分享了一个演讲:在ASP.NET应用中执行后台任务.其中介绍了三种技术的应用:QueueBackgroundWorkItem.Hangfi ...
- ZHA profile与ZLL profile的一个例子
ZHA Coordinator 如何控制ZLL Light/Philips Hue Light 缩写: ZHA: ZigBee Home Automation profile ZLL: ZigBee ...
- 转载:《TypeScript 中文入门教程》 12、类型推导
版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 这节介绍TypeScript里的类型推论.即,类型是在哪里如何被推断的. 基础 ...
- Mybatis中javaType和jdbcType对应关系
JDBC Type Java Type CHAR String VARCHAR String LONGVARCHAR ...
- 怎么用SAX生成xml文件
public void createXML() throws Exception{ Book b1 = new Book(); b1.setId("1"); b1.setName( ...
- AbpKernelModule
PreInitialize IocManager.AddConventionalRegistrar(new BasicConventionalRegistrar()); 增加BasicConventi ...
- Lind.DDD.Repositories.EF层介绍
回到目录 Lind.DDD.Repositories.EF以下简称Repositories.EF,之所以把它从Lind.DDD中拿出来,完全出于可插拔的考虑,让大家都能休会到IoC的魅力,用到哪种方法 ...
- JQ基础之选择器
JQ选择器与CSS/CSS3选择器非常类似,对于学过CSS/CSS3的朋友来说可以是快速上手,下面总结了比较常用的选择器. ID选择器 $('#one') ( '#'代表ID ) CLASS选择器 $ ...

