如何确保在.NET中正确处理对象?

我使用连续运行的C#在.NET 2中创建了一个Windows窗体应用程序。 对于大多数帐户我很满意,但据报道,它偶尔会失败。 我能够在50%的时间内监控其性能,我从未注意到失败。

在这一点上,我担心程序可能使用了太多资源,并且在不再需要时不会处理资源。

正确处理已创建计时器和图形路径,SQL连接等图形对象的创建对象的最佳实践是什么?或者我可以依靠dispose方法来处理所有垃圾收集?

另外:有没有办法可以监控应用程序使用的资源?

最佳做法是确保在不再需要对象时,实现IDisposable接口的所有对象都被称为Dispose on。

这可以使用using关键字或try / finally结构来完成。

在具有为表单生命周期分配的资源的WinForms表单中,需要采用稍微不同的方法。 由于表单本身实现了IDisposable,这表明在某个时间点将在此表单上调用Dispose。 您希望确保您的可支配资源同时处理。 为此,您应该覆盖表格Dispose(bool disposing)方法。 实现应该如下所示:

protected override void Dispose(bool disposing) { if (disposing) { // dispose managed resources here } // dispose unmanaged resources here } 

关于表单中组件的注释:如果对象实现了IComponent接口,则可以将实例放在表单Container中 。 当容器本身被丢弃时,容器将负责处理组件。

除了已经说过的内容之外,如果你正在使用COM组件,那么确保它们完全发布是一个非常好的主意。 我有一个我一直用于COM版本的片段:

 private void ReleaseCOMObject(object o) { Int32 countDown = 1; while(countDown > 0) countDown = System.Runtime.InteropServices.Marshal.ReleaseCOMObject(o); } 

您应该在稀缺资源上调用Dispose来释放它们。 您可以使用using语句:

 using (var resource = new MyScarceObject()) { // resource will be used here... } // will free up the resources by calling Dispose automatically 

有几种方法可以确保这一点。 我找到的主要帮助是使用“using”关键字。 这适用于:

 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时,保持与db连接打开的一个连接的性能更高。 因此无法逃避这种需要。

在这种情况下,您不能使用“使用”,但您仍希望能够轻松回收连接所拥有的资源。 您可以使用两种机制来获取这些资源。

一个是通过终结者。 超出范围的所有托管对象最终都由垃圾收集器收集。 如果您已定义了终结器,则GC将在收集对象时调用此方法。

 public class MyClassThatHoldsResources { private Brush myBrush; // this is a finaliser ~MyClassThatHoldsResources() { if(myBrush != null) myBrush.Dispose(); } } 

但是上面的代码很遗憾。 原因是因为在最终确定时,您无法保证已经收集了哪些管理对象,哪些尚未收集。 以上示例中的“myBrush”可能已被垃圾收集器丢弃。 因此,最好使用finaliser来收集托管对象,它的用途是整理非托管资源。

终结者的另一个问题是它不具有确定性。 让我们说例如我有一个通过串口通信的类。 一次只能打开一个到串口的连接。 因此,如果我有以下课程:

 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书中推荐的模式是按如下方式实现:

 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); } } 

一些提示:

– 尽可能利用Using()关键字。 如果您有unit testing,我建议您重构代码以实现此更改。

http://msdn.microsoft.com/en-us/library/yh598w02.aspx

– 记住显式取消注册所有事件处理程序,并从应用程序整个持续时间内的列表中删除所有对象。 这是程序员在.NET中最常见的错误,导致这些项目无法收集。

至于监视内置的perfmon ( 2 )可以正常使用内存等。如果你担心文件句柄,dll句柄等,我推荐Process Explorer和Process Monitor

当对象不可访问时,将调用Object.Finalize方法 。 在实现IDisposable的类中禁止这种不必要的调用很有帮助。 您可以通过调用GC.SuppressFinalize方法来完成此操作

 public void Dispose() { // dispose resources here GC.SuppressFinalize(this); }