有几种方法可以确保这一点。我找到的主要帮助是利用“使用”关键字。这是这样应用的:
using(SqlConnection connection = new SqlConnection(myConnectionString))
{
/* utilise the connection here */
}
这基本上翻译成:
SqlConnection connection = null;
try
{
connection = new SqlConnection(myConnectionString);
}
finally
{
if(connection != null) connection.Dispose();
}
因此,它仅适用于实现 IDisposable 的类型。
此关键字在处理 GDI 对象(如钢笔和画笔)时非常有用。但是,在某些情况下,您可能希望将资源保留更长的时间,而不仅仅是方法的当前范围。作为一项规则,如果可能,最好避免这种情况,但例如在处理 SqlCe 时,保持与数据库的一个连接持续打开会更高效。因此,人们无法逃避这种需求。
在这种情况下,您不能使用“使用”,但您仍然希望能够轻松回收连接所持有的资源。
您可以使用两种机制来取回这些资源。
一个是通过终结器。所有超出范围的托管对象最终都由垃圾收集器收集。如果你定义了一个终结器,那么 GC 将在收集对象时调用它。
public class MyClassThatHoldsResources
{
private Brush myBrush;
// this is a finaliser
~MyClassThatHoldsResources()
{
if(myBrush != null) myBrush.Dispose();
}
}
然而,不幸的是,上面的代码是废话。原因是在最终确定时您无法保证哪些托管对象已被收集,哪些尚未收集。因此,上面示例中的“myBrush”可能已经被垃圾收集器丢弃了。因此最好不要使用终结器来收集托管对象,它的用途是整理非托管资源。
终结器的另一个问题是它不是确定性的。例如,假设我有一个通过串行端口进行通信的类。一次只能打开一个到串行端口的连接。因此,如果我有以下课程:
class MySerialPortAccessor
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
~MySerialPortAccessor()
{
if(m_Port != null) m_Port.Dispose();
}
}
如果我这样使用对象:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
private static void Test2()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
我会遇到问题。问题是终结者不是确定性的。也就是说我不能保证它什么时候会运行,因此可以处理我的串口对象。因此,当我运行测试 2 时,我可能会发现端口仍然打开。
虽然我可以在 Test1() 和 Test2() 之间调用 GC.Collect() 来解决这个问题,但不推荐。如果您想从收集器中获得最佳性能,那就让它自己做吧。
因此,我真正想做的是:
class MySerialPortAccessor : IDispable
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
public void Dispose()
{
if(m_Port != null) m_Port.Dispose();
}
}
我会这样重写我的测试:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
private static void Test2()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
现在可以使用了。
那么终结者呢?为什么要使用它?
非托管资源和不调用 Dispose 的可能实现。
作为其他人使用的组件库的编写者;他们的代码可能会忘记处理对象。其他东西可能会终止该进程,因此 .Dispose() 不会发生。由于这些情况,应实施终结器以将任何非托管资源作为“最坏情况”的情况进行清理,但 Dispose也应该整理这些资源,以便您拥有“确定性清理”例程。
所以最后,.NET Framework Guidelines book 中推荐的模式是按以下方式实现两者:
public void SomeResourceHoggingClass, IDisposable
{
~SomeResourceHoggingClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
// virtual so a sub class can override it and add its own stuff
//
protected virtual void Dispose(bool deterministicDispose)
{
// we can tidy managed objects
if(deterministicDispose)
{
someManagedObject.Parent.Dispose();
someManagedObject.Dispose();
}
DisposeUnmanagedResources();
// if we've been disposed by .Dispose()
// then we can tell the GC that it doesn't
// need to finalise this object (which saves it some time)
//
GC.SuppressFinalize(this);
}
}