.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指针,表示一种未知类型. ...
随机推荐
- Hexo主题实现多级分类显示
前言 最近在搞一个博客,是托管在github和gitcafe上的,利用Hexo生成的.之后,发现一个问题,显示的分类都是一级的.而我想要的是:能显示多级分类,层次分明`的那样. 问题 基本主题自带的分 ...
- Genesis2000使用c#开发脚本
这是我自学程序以来在博客园的第一篇博客,如有不好的地方请大家指正,谢谢! 这边文章的目的是给予那些在PCB使用Genesis2000程序脚本开发的人员提供.net平台下的开发方法. 目前genesis ...
- ubuntu入门
Ubuntu的发音 Ubuntu,源于非洲祖鲁人和科萨人的语言,发作 oo-boon-too 的音.了解发音是有意义的,您不是第一个为此困惑的人,当然,也不会是最后一个:) 大多数的美国人读 ubun ...
- C# 特性学习之一、CallerMemberName、CallerFilePath和CallerLineNumber
在开发中经常会写个公有静态类记录日志,如下: /// <summary> /// Writes the error. /// </summary> /// <param ...
- 查找html节点的方法
document.firstChild document.documentElement(兼容性较好) 查找body节点的方法 document.firstChild.lastChild docume ...
- Oracle取消用户连续登录失败次数限制
当用户连续登录失败次数过多时,Oracle会锁定该用户,“FAILED_LOGIN_ATTEMPTS”用于设置最大次数,超过该值则锁定该帐号. 要取消用户连续登录失败次数的限制可以按照以下方法操作: ...
- spring面试题(2)
f-sp-1. Spring的aop你怎样实现? 用动态代理和cglib实现,有接口的用动态代理,无接口的用cglib f-sp-2. Spring在SSH起什么作用 整合作用 f-sp-3. Spr ...
- 初识Spring MVC
1.什么是Spring MVC? Spring MVC属于SpringFrameWork的后续产品,它提供了构建 Web 应用程序的全功能 MVC 模块,与Struts2一样是一种优秀MVC框架,不同 ...
- 工业串口和网络软件通讯平台(SuperIO 2.1)更新发布
SuperIO 2.1下载 一.SuperIO 的特点: 1) 能够很快的构建自己的通讯平台软件,包括主程序. 2) 设备模块化开发,通过配制文件挂载,即可在平台软件下运行. 3) 设备 ...
- JSTL标签功能集锦
1.<fmt:parseNumber integerOnly="true" value="2/3" /> 结果为0 功能:fmt:parseNumb ...