CLR支持两种类型:引用类型和值类型,它们的区别是在内存分配方式上的差异:引用类型是从托管堆上分配的;值类型是在线程栈上分配的。而CLR的垃圾回收是针对托管堆的,因此值类型不受垃圾回收器的控制。
在FCL中,所有称为“结构”(struct)的类型都是值类型,所有称为“类”(class)的类型都是引用类型。所有的Struct都直接派生自抽象类System.ValueType,而System.ValueType直接从System.Object派生。所有的枚举都直接从System.Enum派生,而后者又派生自System.ValueType,所以枚举也是值类型。由于CLR的单继承规则,所以我们在定义值类型时,不能指定基类型,但可以实现接口。同时从下图生成的IL也可以看出,值类型是隐式密封的(sealed),也就是说也不能从值类型派生。
虽然引用类型与值类型实质只是内存分配上的差异,但这种差异会导致两种类型在行为表现上有着明显不同,比如下面的例子:
struct ValType { public int x;}
class RefType { public int x;}
class Program
{
static void Main(string[] args)
{
ValType v1 = new ValType(); //在栈上分配内存
RefType r1 = new RefType(); //在堆上分配内存
v1.x = 2;
r1.x = 2;
//执行到这里,内存结构请见图1
Console.WriteLine(v1.x); //2
Console.WriteLine(r1.x); //2
ValType v2 = v1; //在栈上分配内存(v2),并把v1栈的内容复制到v2
RefType r2 = r1; //把r1的堆地址复制给r2
v2.x = 5; //只改变v2栈的内容
r2.x = 5; //由于r2和r1都引用同一个堆上的对象,改变r2也会改变r1
//执行到这里,内存结构请见图2
Console.WriteLine(v1.x); //2
Console.WriteLine(r1.x); //5 注意这里变成了r2修改后的值
Console.WriteLine(v2.x); //5
Console.WriteLine(r2.x); //5
Console.ReadKey();
}
}
首先我们定义一个一值类型与一个引用类型,内部都只有一个字段。用new操作符分配内存时,值类型v1的内存分配在了线程栈上,引用类型r1的内存分配在了托管堆上,在程序运行到第一次WriteLine输出时,看到的结果是一致的。但接下来声明两个新的对象并执行赋值时,这里的发生的事明显不同:虽然赋值操作都是拷贝线程栈上变量的内容,但由于值类型变量v1的栈内容就是ValType类型实例本身,而引用类型r1的栈内容是RefType对象实例在堆上的地址。所以赋值后的结果就是,v1和v2各保存了一份ValType类型实例,而r1和r2保存了同一块堆内存的地址。所以改变r2对象导致了r1对象的随同改变。下面是内存示意图:
图1
图2
虽然值类型实例不需要垃圾回收,但由于值类型在传递时,传递的是内容本身,所以并不适合将所一些实例较大的类型定义为值类型。实现上除非满足以下所有条件,否则不应该将一个类型声明为值类型。
-
没有更改其字段的成员,即该类型是不可变的。(建议所有字段为readonly)
-
类型不需要从其他任何类型继承。(值类型不能选择基类)
-
类型也不会派生出其他任何类型。(所有的值类型都是隐式密封sealed的)
-
实例较小(约<=16Byte)或较大但不作为方法实参传递,也不从方法返回。
值类型的装箱与拆箱
将值类型转换成一个引用类型的过程叫装箱,整个过程看起来是这样的:
- 在托管堆中分配好内存,分配的内存量=值类型的各个字段所需的内存量+所有堆上对象都有的两个额外成员(类型对象指针和同步块索引)所需的内存量。
- 值类型的字段复制到新分配的内存。
- 返回对象的地址。
拆箱仅是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)。虽然拆箱比装箱代价低,但实际在拆箱之后往往紧接着就是赋值操作(内存复制)。显然装箱和拆箱/复制会对应用程序的速度与内存消耗上产生不利影响,所以应该了解到这一点,并尽量避免装箱和拆箱操作。那么什么时候会发生装箱和拆箱,最直观的方法就是看生成的IL代码(IL对应指令是分别是box与unbox),比如下面的例子:
示例中ArrayList的Add方法参数是Object类型,也就是说一个引用类型(在堆上分配的内存),当我们传递int类型时,这里便会将int实例装箱,以返回一个堆上的地址。在将array[0]强制转型为int时,由于值类型int的对象是在线程栈上分配的,所以这里拆箱并紧接着发生赋值(内存复制)操作。同时为了对比,我加了引用类型的reference,可以看出引用类型是不会发生装箱与拆箱的。
那么如何避免(或减少)装箱与拆箱:
-
尽量使用泛型集合。
-
尽量将装箱与拆箱操作移到循环体之外。
-
定义一个方法如果可接收引用类型或值类型时,尽量不要将参数定义为object,可以考虑通过重载定义多个版本或定义泛型方法。
<wbr><a target="_blank" rel="nofollow" href="http://www.cnblogs.com/hecool/p/3149833.html">原文地址</a></wbr>
分享到:
相关推荐
C#中引用类型和值类型的区别C#中引用类型和值类型的区别
C#中除了基本类型以外的类型都是引用类型。引用类型的特点是,在堆栈中存储的是该引 用类型指向的堆中的“地址”。所以,当引用类型之间相互赋值的时候,只是将堆栈中的值 (可以理解为所指向的“堆”地址)相互赋值...
值类型直接存储其值,变量本身就包含了其实例数据,而引用类型保存的只是实例数据的内存引用。因此,一个值类型变量就永远不会影响到其他的值类型变量,而两个引用类型变量则很有可能指向同一地址,从而发生相互影响...
c# 值类型 引用类型 内存分析 图解 ,从根本上理解值类型和引用类型变量的本质区别。
C#中引用类型和值类型 包含了C#中的所有引用类型和值类型的分类,别对于各个类型的关键字,给出来相应的例子。值得参考。
一起回忆那些年美好的回忆吧,缅怀青春,昂首迎接新的挑战,我们可以走的更远!
值类型和引用类型的区别: 似乎“值类型和引用类型的区别”是今年面试的流行趋势,我已然是连续三次(目前总共也就三次)面试第一个问题就遇到这个了,这是多大的概率啊,100%. 言归正传,咱还是先来探讨探讨这二者...
C#值类型与引用类型区别,和一些基础算法,刚入门的小伙伴,希望对你有用
用Jeffrey Richter(《CLR via C#》作者)的话来说,“不理解引用类型和值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能问题”。这就要求我们正确理解和使用值类型和引用类型。值类型包括C#的基本类型(用...
C#只有两种数据类型:值类型和引用类型 值类型在线程栈分配空间,引用类型在托管堆分配空间 值类型转为引用类型称成为装箱,引用类型转为值类型称为拆箱 以下是值类型和引用类型对照表 从上图可以简单看出:string...
本资源主要包含了C#中的值类型和引用类型有关资料
7理解C#值类型与引用类型[整理].pdf
C#数据类型,变量的声明,定义使用(值类型,引用类型等)
第四章 C#预定义值类型和引用类型.docx
枚举,结构,值型和引用类型的资料,很详细实用的资料啊,希望对大家有用。
C#值类型和引用类型及参数传递[收集].pdf
值类型和引用类型………………………………………………………………………………………………………………………………
C#的预定义数据类型包括两种,一种是值类型,一种是引用类型。值类型的变量在内存中是存储在堆栈中的,字面上理解就是直接保存其值,如声明一个属于值类型的整型变量,并给它赋予另一个整型变量的值,则在内存中事先...
主要介绍了c#值类型和引用类型使用示例,需要的朋友可以参考下
主要给大家介绍了关于c#基础系列之值类型和引用类型的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧