.NET 类型(Types)的那些事

引言

您是.Net工程师?那 .NetFramework中的类型您知道有三大类吗?(除了引用类型和值类型,还有?)

引用类型一定在“堆”上,值类型一定在“栈”上?

那引用类型在内存中的布局细节您又知道多少了?

 

.Net Framework 中的Types分类

 

09fig01

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,如下图;

value_type

  • 把一个引用类型a(定义如下Employee a=new employee();)赋给另外一个引用类型b(Employee b;),即(b=a;)时,会把a的地址(引用)拷贝一份给a,即他们指向同一个地址;

reference_type1

开始代码讲解,首先看代码如下:

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)

 

上面这段代码的内存中的分配,示意图:

image006

很清楚地看到

  • Size类型的s分配到了Stack上,而Front类型的f 和Form类型的myForm则分配在堆上。
  • 并且myForm的Font属性引用到了Font类型f。
  • myForm的Size属性有它自己的值(Width和Height),它是Size类型s的一个拷贝。

这里我们更清晰的看到了值类型和引用类型在值赋值过程中的区别

我们可以通过修改Font类型f的值,来修改myForm中的字体样式,但不能通过修改s来修改myFrom的Size。

引用类型的Object内存布局基础结构

image_thumb_2

上面这个图展示的结构是通过分析源码得出的:

  1. 首先ObjectHeader(在其所在的AppDomain中的那个Thread通过调用Monitor.Enter锁了这个对象)
  2. 接下来是Method Table 指针(该指针指向AppDomain中声明(定义)托管类型),如果程序集被加载到AppDomain neutral 中,那么所有的AppDomain中该类型实例的Method Table指针都一样。CLR 类型系统的该基础构建块在托管代码中都是可视的。(TypeHandle.Value 是一个IntPtr
  3. 最后就是这部分就是该类型实例的值。

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)
x8612 bytes = 2*4+4
x64 24 bytes = 2*8+8

 

引用类型的Object内存布局代表性结构

 

普通对象

 

012302077246530

数组对象 - Array

012302218801605

字符串对象

012302322553069

 

Boxing,小心您的值类型不经意间被装箱

 

int a=1;
object b=a;

这段代码大家都知道会发生装箱,装箱后原来的值类型会有哪些变化?看下装箱和拆箱的步骤:

装箱:
对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。
第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
第二步:将值类型的实例字段拷贝到新分配的内存中。
第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
有人这样理解:如果将Int32装箱,返回的地址,指向的就是一个Int32。我认为也不是不能这样理解,但这确实又有问题,一来它不全面,二来指向Int32并没说出它的实质(在托管堆中)。
拆箱:
检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。
有书上讲,拆箱只是获取引用对象中指向值类型部分的指针,而内容拷贝则是赋值语句之触发。我觉得这并不要紧。最关键的是检查对象实例的本质,拆箱和装箱的类型必需匹配,这一点上,在IL层上,看不出原理何在,我的猜测,或许是调用了类似GetType之类的方法来取出类型进行匹配(因为需要严格匹配)。

那么给你个自定义结构体,你还清楚什么情况会被装箱吗?

参考《防止装箱落实到底,只做一半也是失败

附加

为方便大家查看源码,这里提供一个源码索引表

SSCLI文件索引

ItemSSCLI 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对象的内存布局

托管堆与垃圾收集

C# 装箱和拆箱[整理]

mdsn 类型详解(Jit and Run)

转载于:https://www.cnblogs.com/HQFZ/p/4638794.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值