参考书:《 visual C# 从入门到精通》
第二部分 理解C#对象模型
第14章 使用垃圾回收和资源管理
我们知道,创建对象要用new
关键字,
Square mysquare = new Square();
new
实际上是分两步的:
new
操作从堆中分配原始内存,这个阶段无法进行干预
new
操作将原始内存转换成对象:它必须初始化对象。可用构造器控制这一阶段
当变量mysquare
离开作用域时,它引用的对象就没有引用了,这样对象会被销毁,占用的内存被回收。对象的销毁也分来两步走:
1. `CLR`执行清理工作,可以写一个析构器来控制
2. `CLR`将对象占用的内存归还给堆,解除对象内存的分配。对这个阶段我们没有控制权。
销毁对象并将内存归还给堆的过程称为垃圾回收。
用析构器可以在对象被垃圾回收时执行必要的清理。CLR
可以自动清理对象使用的任何托管,所以很多时候时不需要自己写析构器的。但要是托管资源很大(如多维数组),就可以考虑对该资源的所有引用都设为null
,使资源能被立即清理。如果对象引用了非托管资源,析构器就更有用了。
构造器的语法是~
+类名,如下:
class FileProcessor{
FileStream file=null;
public FileProcessor(string fileName){
this.file=File.OpenRead(fileName);
}
~FileProcessor(){
this.file.Close();
}
}
注意以下几点:
struct
)编译器内部自动将构造器转换成对Object.Finalize
方法的一个重写版本的调用。如:
class FileProcessor{
-FileProcessor(){//你的代码}
}
编译器转换成如下的形式:
class FileProcessor{
protected override void Finalize(){
try{//你的代码
}
finally{base.Finalize();}
}
}
C#中我们总是不能自己销毁对象,而是由CLR
在它认为合适的时间帮你做这件事。由于一个对象可能会有多个引用,CLR
必须跟踪所有的引用。对象的生存期不能和特定的引用变量绑定。只有在一个对象的所有引用都消失后才可以销毁该对象,回收其内存。
如果由程序员负责销毁对象,很有可能会遇到以下的情况:
而垃圾回收器可以做到以下几点担保:
注意,垃圾回收并不是在对象不再需要时立即进行。垃圾回收是一个代价较高的过程,所以只在觉得有必要时才会进行垃圾回收。
可以通过静态方法System.GC.Collect
在程序中调用垃圾回收器(回收过程时异步发生的),但不建议这样做。
垃圾回收器在它自己的线程中运行。它运行时应用程序中的其他线程将暂停,因为垃圾回收器可能需要移动对象并更新对象引用。
它的步骤大体如下:
freachable
的特殊队列中一个类中包含析构器会使代码和垃圾回收过程变得复杂,同时会影响程序的运行速度。如果程序中不包含析构器,垃圾回收器就不需要将不可达对象放到freachable
队列并对他们进行“终结”。所以除非确实有必要,最好尽量避免使用析构器。同时要确定析构器不相互依赖或相互重叠。
有点资源比较宝贵,用完后应该马上释放,这时就需要通过自己写的资源清理方法亲自释放资源。显式调用类的资源清理方法控制资源是释放的时机。
比如来自System.IO
命名空间中的TextReader
类,该类提供从顺序输入流中读取字符的机制。TextReader
包含虚方法Close
,它负责关闭流,这是一个资源清理方法。StreamReader
类从流中读取字符,StringReader
类从字符串中读取字符。这两个类都从TextReader
类派生,都重写了Close
方法。如使用StreamReader
类从文件中读取文本行并在屏幕上显示:
TextReader reader =new StreamReader(filename);
string line;
while((line=reader.ReadLine())!=null){
Console.WriteLine(line);
}
reader.Close();
上述代码的非常重要的一点是调用Close
方法来释放文件句柄以及相关资源。但有一个问题是它不是异常安全的。如果对ReadLine
或WriteLine
方法的调用抛出异常,对Close
的调用就不会发生了,这样可能会耗尽文件句柄资源,无法打开更多文件。
为了解决上面的问题,可以在finally
块中调用Close
方法:
TextReader reader=new StreamReader(filename);
try{
string line;
while((line=reader.ReaderLine())!=null){
Console.WriteLine(line);
}
}
finally{
reader.Close();
}
但它存在几个缺点:
try
和finally
finally
块之后的作用域中,这意味着可能不小心使用一个已释放的资源using
语句和IDisposable
接口using
语句提供了一个脉络清晰的机制来控制资源的生存期。语法如下:
using(type variable=initialzation){
...;
}
如:
using(TextReader reader=new StreamReader(filename)){
string line;
while((line=reader.ReadLine())!=null){
Console.WriteLine(line);
}
}
它完全等价于前面用finally
块的形式:
{
TextReader reader=new StreamReader(filename);
try{
string line;
while((line=reader.ReaderLine())!=null){
Console.WriteLine(line);
}
}
finally{
reader.Close();
}
}
using
语句定义了一个作用域。
using
语句声明的类型必须实现IDisposable
接口。IDisposable
在System
命名空间中,只包含一个名为Dipose
的方法。
namespace System{
interface IDisposable{
void Dispose();
}
}
而StreamReader
类实现了这个接口,它的Dispose
方法会调用Close
方法来关闭流。using
语句解决了finally
块可能出现的所有问题。
Dispose
方法实现IDisposable
接口的示例如下:
Class Example:IDisposable{
private Resource scarce;
private bool dispose=false;
...;
~Example(){
this.Dispoable(false);
}
public virtual void Dispose(){
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing){
if(!this.disposed){
if(disposing){
...;
}
this.disposed=true;
}
}
public void SomeBehavior(){
checkIfDisposed();
...;
}
private void checkIfDisposed(){
if(this.disposed){
throw new ObjectDisposedException("示例:对象已经清理");
}
}
}
注意:
Dispose
方法可以安全的多次调用。变量disposed
指出方法以前是否运行过,这样防止在并发调用方法时资源被多次清理。方法只有在第一次运行才会清理资源Dispose
方法支持托管资源和非托管资源的清理Dispose
方法调用静态GC.SuppressFinalize
方法。该方法阻止垃圾回收器为这个对象调用析构器,因为对象已经终结了最后放上一个using
语句的示例:
Calculator.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace C_14_3
{
class Calculator:IDisposable
{
private bool disposed = false;
public int Divide(int first,int second)
{
return first / second;
}
public void Dispose()
{
if (!disposed)
{
Console.WriteLine("Calculator being disposed");
}
this.disposed = true;
GC.SuppressFinalize(this);
}
public Calculator()
{
Console.WriteLine("Calculator being created");
}
~Calculator()
{
Console.WriteLine("Calculator being finalized");
this.Dispose();
}
}
}
Program.cs
using System;
namespace C_14_3
{
class Program
{
static void Main(string[] args)
{
using(Calculator calculator1=new Calculator())
{
Console.WriteLine($"120/0={calculator1.Divide(120, 0)}");
}
Console.WriteLine("Program finishing");
}
}
}
using
语句中故意引发异常,观察结果,程序最后还是调用了Dispose
方法来回收资源,运行结果为