C#中值类型和引用类型
概念:
1.值类型:数据存储在内存的堆栈中,从堆栈中可以快速地访问这些数据,因此,值类型表示实际的数据。
2.引用类型:表示指向存储在内存堆中的数据的指针或引用(包括类、接口、数组和字符串)。
C#中定义的值类型包括原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct)
引用类型包括:类、数组、接口、委托、字符串等。
区别:
基本区别在于它们在内存中的存储方式。值类型只将值存放在内存中,这些值类型都存储在堆栈中。原始数据类型(如bool和int)都属于此类型。而引用类型的内存单元中只存放内存堆中对象的地址,而对象本身放在内存堆中。如果引用的值类型的值是null,则表示未引用任何对象。
堆和堆栈区别:
堆和堆栈是两个不同的概念,在内存中的存储位置也不相同,
堆一般用于存储可变长度的数据,如字符串类型;
堆栈则用于存储固定长度的数据,如整型类型的数据int(每个int变量占用四个字节)。由数据存储的位置可以得知,当把一个值变量赋给另一个值变量时,会在堆栈中保存两个完全相同的值;而把一个引用变量赋给另一个引用变量,则会在堆栈中保存对同一个堆位置的两个引用,即在堆栈中保存的是同一个堆的地址。在进行数据操作时,对于值类型,由于每个变量都有自己的值,因此对一个变量的操作不会影响到其它变量;对于引用类型的变量,对一个变量的数据进行操作就是对这个变量在堆中的数据进行操作,如果两个引用类型的变量引用同一个对象,实际含义就是它们在堆栈中保存的堆的地址相同,因此对一个变量的操作就会影响到引用同一个对象的另一个变量。
为了更好地说明两种类型之间的区别,借用如下的表格来说明。
值类型 | 引用类型 | |
内存分配地点 | 分配在栈中 | 分配在堆中 |
效率 | 效率高,不需要地址转换 | 效率低,需要进行地址转换 |
内存回收 | 使用完后,立即回收 | 使用完后,不是立即回收,等待GC回收 |
赋值操作 | 进行复制,创建一个同值新对象 | 只是对原有对象的引用 |
函数参数与返回值 | 是对象的复制 | 是原有对象的引用,并不产生新的对象 |
类型扩展 | 不易扩展 | 容易扩展,方便与类型扩展 |
注:GC(Garbage Collector,垃圾回收器)是一种自动回收内存的机制,释放已经不再使用的对象的内存空间。
在.NET平台中,我们的托管代码一般都不再关心内存的管理,一切都有CLR(Common language Runtime)去帮我们完成了。当我们开辟内存空间用来创建对象时,使用new关键字,这时CLR会分配一块内存存放对象,大部分时候,我们都不用自己去释放内存空间,而是由CLR在某个适当的时候帮我们释放掉。
为什么要GC?
1.创建新对象开辟内存空间,在使用完后需要释放内存,提高性能
2.避免开发人员直接操作内存,提高安全性
GC(回收)过程
我们运行.NET程序后,OS Loader首先识别出IL(中间语言),然后会加载CLR的核心库,进行一系列的必要处理后,CLR来到我们编写的代码入口处执行。
当我们的在代码中使用new操作符创建class时,CLR便在叫作GC堆(GC Heap)的内存区域上分配一块内存存放我们的对象,若对象的Size超过85K字节时,考虑到性能原因,将对象创建在LOH(Large Object Heap)上而不是GC堆上【注1】,若我们在class中定义了析构函数来释放非托管资源【注2】,则CLR会在一个叫做终结器队列(Finalizer Queue)的地方添加一个指向该class的项。
我们的程序在运行的过程,在某个时候需要进行垃圾回收了【注3】,首先GC会暂时挂起所有线程,然后确定对象引用的roots【注4】,并根据引用关系创建出由roots出发可以达到的对象形成的对象图,这些对象暂时还在使用,而那些已创建的却不在对象图中的对象则是不可达到的,也就是垃圾了,属于要回收的对象。随后将仍然使用的对象移动到存活期更久的区域【注5】,更改区域指针以回收对象,压缩内存去除内存空隙,并修复对移动的仍存活对象的引用指针,对于有析构函数的对象,则第一次回收时不会回收,而是将其在终结器队列中移除,并添加到另一个标为准备终止的对象列表中,另一个GC线程会调用此列表指向的对象的Finalize(),回收非托管资源,然后将项从列表中移除,下一次的GC才会真正回收掉该对象。
注1:对象创建在Heap上的细节
1): 为了更高效的进行GC,.NET将GC堆分成了3个代,Gen0,Gen1和Gen2。
2): 这3个代只是逻辑上的划分,在内存中,他们的地址是连续的。
3): Gen0和Gen1之和的大小大约是16M(workstation GC模式下)和64M(server GC模式下)。
4): 新创建对象Size小于85k位于Gen0上,大于85K的则创建在LOH上。
注2:定义析构函数释放非托管资源
Finalize方法是用来释放对象中使用的非托管资源,他是作为Dispose()方法的一种安全防护措施,即代码中没有显示的调用Dispose()来释放非托管资源时,GC时调用Finalize方法来释放,Finalize方法中并不直接释放非托管资源,而是调用Dispose(false)来释放。自.NET2.0起,C#中不能直接override Finalize方法,是通过析构函数来实现,析构函数在IL中会被解释为: