我的看法和见解,有什么不对的地方希望大家提出,还有我提出的问题,也希望大家讨论一下。(我学习这部分知识所用书籍是《.NET框架程序设计》李建中译,《effective C#》李建中译)。
1.首先说一下非托管资源。有的资源,对于.net的运行时来说,是没有办法托管的,比如文件句柄,Win32的互斥体(Mutex对象),数据库链接等。因为他们都是和操作系统或者是数据库打交道,这些资源是非.net的资源。所以像sqlConnection,Mutex,FileStream这些类,比较特殊。有的书说什么托管资源如果不调用dispose,.net就不知道怎么去释放它。 其实包含了托管资源的这些类,他们都实现了dispose和finalize,所以第一次垃圾回收(当对象不可到达之后),就会调用finalize方法,释放非托管资源,第2次再回收本来的内存。(这里不考虑代龄的问题)。
2. 下面看看书上写的Dispose的实现:class MyClass:IDisposable
{
private SqlConnection sqlCon = new SqlConnection();
private bool disposed = false; public void Dispose()
{
Dispose(true);
GC.suppressFinalize(this);
}
~MyClass()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
try
{
lock (this)
{
if(disposed) //已经调用过Dispose了就直接返回
{
return;
}
if(disposing) //表示是手动调用Dispose的
{
//释放托管资源(很多书都这样写,我真是不懂到底什么意思,怎么去释放托管资源)
}
//书中说下面的代码是finalize和dispose都要执行的代码,因为finalize是垃圾回收的时候执行
//类中的内部对象可能在该finalize执行的时候已经被回收了,所以不应该引用其他内部对象
sqlCon.Dispose(); }
}
finally
{
sqlCon.Dispose();
disposed = true;
}
}这里的问题: 在if(disposing){} 中,到底书上说的释放托管资源是什么意思呢?其实这里可以访问对象中的任何资源这个我是知道的,因为是手动调用嘛,所有东西都是活的。
但是sqlCon的调用,为什么可以写在if(disposing)的外面呢? 因为MyClass的finalize调用的时候,sqlCon一定没有释放吗?其实我测试过,如果一个实现了finalize的类中有另一个也实现了finalize的对象,内部对象的finalize通常都会先执行。而sqlCon也是实现了finalize的,所以我想sqlCon的finalize也一定实现执行,只是我们看不到。当然,执行了finalize,只是这个对象调用了一个函数而已,其实它的内存还在,所以调用dispose再让它做点事,是可以的(因为sqlCon的finalize已经执行了,所以sqlCon.Dispose方法在这种情况下是直接返回的,没有做任何事情,因为sqlCon的非托管资源已经释放了)。
那我现在的情况是这样的 , MyClass中包含了一个对象 b, 而b中又包含了一个sqlConnection对象,b实现了标准的dispose方,如下:
class B:IDisposable (具体如何实现就不写了)
{
sqlConnection sqlCon;
}
MyClass:Idisposeble
{
b;
protected virtual void Dispose(bool disposing)
{
// ....其他部分省略
if(disposing)
{
.....
}
b.Dispose(true),应该在这里调用吗?
也就是因为b包含了非托管的资源,所以做法和一般的sqlConnection相同
}
}我这么说对吗? b.Dispose(true)应该放在if(disposing)块的外面。???????????????所以,所谓的if(disposing)块之外,不应该访问类的内部成员,应该是错的吧,应该是只应该做资源的释放,也就是只应该调用Dispose方法。 原因就是因为这些对象虽然可能已经执行过finalize了. 而如果是没有实现Finalize方法的对象,他们有可能已经是null了。
可能我的叙述有点混乱 ,但是还是希望大家能讨论下。
1.首先说一下非托管资源。有的资源,对于.net的运行时来说,是没有办法托管的,比如文件句柄,Win32的互斥体(Mutex对象),数据库链接等。因为他们都是和操作系统或者是数据库打交道,这些资源是非.net的资源。所以像sqlConnection,Mutex,FileStream这些类,比较特殊。有的书说什么托管资源如果不调用dispose,.net就不知道怎么去释放它。 其实包含了托管资源的这些类,他们都实现了dispose和finalize,所以第一次垃圾回收(当对象不可到达之后),就会调用finalize方法,释放非托管资源,第2次再回收本来的内存。(这里不考虑代龄的问题)。
2. 下面看看书上写的Dispose的实现:class MyClass:IDisposable
{
private SqlConnection sqlCon = new SqlConnection();
private bool disposed = false; public void Dispose()
{
Dispose(true);
GC.suppressFinalize(this);
}
~MyClass()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
try
{
lock (this)
{
if(disposed) //已经调用过Dispose了就直接返回
{
return;
}
if(disposing) //表示是手动调用Dispose的
{
//释放托管资源(很多书都这样写,我真是不懂到底什么意思,怎么去释放托管资源)
}
//书中说下面的代码是finalize和dispose都要执行的代码,因为finalize是垃圾回收的时候执行
//类中的内部对象可能在该finalize执行的时候已经被回收了,所以不应该引用其他内部对象
sqlCon.Dispose(); }
}
finally
{
sqlCon.Dispose();
disposed = true;
}
}这里的问题: 在if(disposing){} 中,到底书上说的释放托管资源是什么意思呢?其实这里可以访问对象中的任何资源这个我是知道的,因为是手动调用嘛,所有东西都是活的。
但是sqlCon的调用,为什么可以写在if(disposing)的外面呢? 因为MyClass的finalize调用的时候,sqlCon一定没有释放吗?其实我测试过,如果一个实现了finalize的类中有另一个也实现了finalize的对象,内部对象的finalize通常都会先执行。而sqlCon也是实现了finalize的,所以我想sqlCon的finalize也一定实现执行,只是我们看不到。当然,执行了finalize,只是这个对象调用了一个函数而已,其实它的内存还在,所以调用dispose再让它做点事,是可以的(因为sqlCon的finalize已经执行了,所以sqlCon.Dispose方法在这种情况下是直接返回的,没有做任何事情,因为sqlCon的非托管资源已经释放了)。
那我现在的情况是这样的 , MyClass中包含了一个对象 b, 而b中又包含了一个sqlConnection对象,b实现了标准的dispose方,如下:
class B:IDisposable (具体如何实现就不写了)
{
sqlConnection sqlCon;
}
MyClass:Idisposeble
{
b;
protected virtual void Dispose(bool disposing)
{
// ....其他部分省略
if(disposing)
{
.....
}
b.Dispose(true),应该在这里调用吗?
也就是因为b包含了非托管的资源,所以做法和一般的sqlConnection相同
}
}我这么说对吗? b.Dispose(true)应该放在if(disposing)块的外面。???????????????所以,所谓的if(disposing)块之外,不应该访问类的内部成员,应该是错的吧,应该是只应该做资源的释放,也就是只应该调用Dispose方法。 原因就是因为这些对象虽然可能已经执行过finalize了. 而如果是没有实现Finalize方法的对象,他们有可能已经是null了。
可能我的叙述有点混乱 ,但是还是希望大家能讨论下。
{
return;
}也不应该放在try中。
按你写的貌似应该是:private void Dispose(bool disposing)
{
try
{
lock (this)
{
if(disposed) //已经调用过Dispose了就直接返回
{
return;
}
if(disposing) //表示是手动调用Dispose的
{
//释放托管资源,如果是你自己定义的类型应该在这释放,datatable也应该在这
// 设有别个类被实例化 YouClass c=new YouClass();//在释放前有实例化的,并且 YouClass同样实现了//IDisposable
c.dispose();
}
//非托管对象释放,
sqlCon.Dispose();
}
}
finally
{
disposed = true;
}
}
using System.ComponentModel;// The following example demonstrates how to create
// a resource class that implements the IDisposable interface
// and the IDisposable.Dispose method.public class DisposeExample
{
// A base class that implements IDisposable.
// By implementing IDisposable, you are announcing that
// instances of this type allocate scarce resources.
public class MyResource: IDisposable
{
// Pointer to an external unmanaged resource.
private IntPtr handle;
// Other managed resource this class uses.
private Component component = new Component();
// Track whether Dispose has been called.
private bool disposed = false; // The class constructor.
public MyResource(IntPtr handle)
{
this.handle = handle;
} // Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
} // Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
private void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
component.Dispose();
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
CloseHandle(handle);
handle = IntPtr.Zero;
}
disposed = true;
} // Use interop to call the method necessary
// to clean up the unmanaged resource.
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle); // Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~MyResource()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
}
public static void Main()
{
// Insert code here to create
// and use the MyResource object.
}
}
/////////////////////////////////////
你说的 YouClass类型的对象c,也实现了 Idisposable接口,那么它内部一定是拥有非托管的资源,比如FileStream.
那么如果像你说的private void Dispose(bool disposing)
{
try
{
lock (this)
{
if(disposed) //已经调用过Dispose了就直接返回
{
return;
}
if(disposing) //表示是手动调用Dispose的
{
//释放托管资源,如果是你自己定义的类型应该在这释放,datatable也应该在这
// 设有别个类被实例化 YouClass c=new YouClass();//在释放前有实例化的,并且 YouClass同样实现了//IDisposable
c.dispose();
}
//非托管对象释放,
sqlCon.Dispose();
}
}
finally
{
disposed = true;
}
}这个c.dispose()在 if(disposing)之内, 那么如果是垃圾回收调用的finalize,那么这个非托管资源在这次就得不到释放哦? 按照本意,一个类的dispose手动调用,就是为了把该对象之内的所有非托管资源都释放(因为非托管资源很宝贵)。 如果像你这样做,根本就没有起到dispose的作用。 比如在MyClass中,有100个YouClass的对象,而YouClass对象里,封装了sqlConnection对象。 如果像你所说的,那么这个YourClass的diapose方法手动调用的话, 根本就没有进行任何行为,100个数据库链接还是要垃圾回收的时候才 会调用其finalize方法。
不然为什么sqlConnection的dispose可以写在 if(disposing)之外, 而b.disposing却不可以? 公共运行时对待sqlConnection和b的做法应该是一样的。他们都拥有非托管资源,他们都实现了IDisposable接口和都重写了Finalize方法。
在 if(disposing)之内是托管资源吧?
按照本意,一个类的dispose手动调用,就是为了把该对象之内的所有非托管资源都释放(因为非托管资源很宝贵)这个是对的
比如在MyClass中,有100个YouClass的对象,而YouClass对象里,封装了sqlConnection对象。如果像你所说的,那么这个YourClass的diapose方法手动调用的话, 根本就没有进行任何行为,100个数据库链接还是要垃圾回收的时候才 会调用其finalize方法。你的例子上没有显示你说的这种情况,所以我没写,实际的应该是要释放的.即如果YouClass里面有非托管资源必须在if(disposing)之外显示disponse();
MSDN原话为:
如果 Object 保存了对任何资源的引用,则 Finalize 必须由派生类重写,以便在垃圾回收过程中,在放弃 Object 之前释放这些资源。 当类型使用文件句柄或数据库连接这类在回收使用托管对象时必须释放的非托管资源时,该类型必须实现 Finalize。
.net其实不建议重写finalize,因为垃圾回收过程中的回收往往比不重写需要更长的时间关于托管资源包含托管资源时,.net具体处理并不是按由内向外或者由外向内的顺便.
垃圾回收过程中执行终结器的准确时间是不确定的。不保证资源在任何特定的时间都能释放,除非调用 Close 方法或 Dispose 方法。即使一个对象引用另一个对象,也不能保证两个对象的终结器以任何特定的顺序运行。即,如果对象 A 具有对对象 B 的引用,并且两者都有终结器,则当对象 A 的终结器启动时,对象 B 可能已经终结了。
有时候当你一个页面关闭想重新加载时,这时这些缓存就会影响到它,所以就要用大disposing立即释放
看看这篇文章吧, 讲的很清楚了
如果 disposing 等于 true,则该方法已由用户的代码直接调用或间接调用,并且可释放托管资源和非托管资源。如果 disposing 等于 false,则该方法已由运行库从终结器内部调用,并且只能释放非托管资源。因为终结器不会以任意特定的顺序执行,所以当对象正在执行其终止代码时,不应引用其他对象。如果正在执行的终结器引用了另一个已经终止的对象,则该正在执行的终结器将失败。所以你的b.Dispose(true) 应该放在if(disposing) 块中。而且不能放在外面,否则该正在执行的终结器将失败。另外,你理解错了,虽然sqlConnection包含非托管资源,但和你的类没有任何关系,你的类包含sqlConnection实例并不表示你也包含非托管资源,
因为sqlConnection的实例是托管对象,它不是非托管资源。当然上面也说了,你也可以实现Dispose来释放托管资源,但建议你只有在sqlConnection的实例在整个类的所有实例方法中都引用的时候才这么做,也就是SQL链接从类初始化就打开,知道类的实例被释放才关闭SQL数据库链接,但你也应该知道这样做将导致一个长期打开的数据库连接,严重影响性能。合适的做法是在使用到SQL链接的方法中立即打开和立即关闭,这样情况下不用实现Dispose。
所以只要你按照NET的设计规范来设计类,那么托管资源永远都会被释放。Dispose(true)只是为了显示释放,以提高应用程序性能:
所以
if(disposing) //表示是手动调用Dispose的
{
//释放托管资源(很多书都这样写,我真是不懂到底什么意思,怎么去释放托管资源)
}
这是正确的,假垃圾程序员没有显示调用Dispose,之后Finalize 调用了Dispose,即Dispose(false),那么没关系,if(disposing)中的托管资源没释放没关系(说不定已经释放了,应为Finalize执行之后类的成员对象已经执行了Finalize),因为if(disposing)都是托管对象,没白没?托管对象啊,垃圾回收器始终会释回收它的,虽然这样会影响性能,可谁叫你不显示调用Dispose???
{
return;
}
if(disposing) //表示是手动调用Dispose的
{
//释放托管资源,如果是你自己定义的类型应该在这释放,datatable也应该在这
// 设有别个类被实例化 YouClass c=new YouClass();//在释放前有实例化的,并且 YouClass同样实现了//IDisposable
c.dispose();
}
//非托管对象释放,
sqlCon.Dispose();
}
6楼的逻辑是正确的,即在if(disposing)块释放托管资源,在外面释放非托管资源,但可能是比误,其实 sqlCon 是托管对象了,也应该在if(disposing)里面
之所以在disposing块之内释放托管资源是因为这个块是用户显式调用时执行的 可以保证所包含的托管资源有效(而不是正在或已经被垃圾收集)
而因为垃圾收集顺序的不确定性 在析构函数中不宜调用true的dispose方法 因为这是很有可能你要释放的托管对象已经被垃圾收集了一次为null了
在 if(disposing)之外,释放非托管资源,这么清楚还没明白?
比如你使用API OpenProcess 打开了一个进程并得到了改进程的句柄,那么按照SDK建议,你使用完了之后应该关闭这个进程,所以你可以在 if(disposing)之外 使用 CloseHandle 来关闭它。