如何在C#中执行C ++样式的析构函数?
我有一个通过IDisposable
带有Dispose
函数的Cclass。它打算在" using"块内使用,以便可以立即释放它处理的昂贵资源。
问题是当在调用Dispose之前引发异常而程序员忽略了使用using或者finally时,发生了一个错误。
在C ++中,我永远不必为此担心。对类的析构函数的调用将自动插入到对象作用域的末尾。避免发生这种情况的唯一方法是使用new运算符并将对象保留在指针后面,但是程序员不需要额外的工作,因为他们不是偶然地做的事情,例如忘记使用" using"。
有什么办法可以在C#中自动使用using
块?
非常感谢。
更新:
我想解释为什么我不接受终结器答案。这些答案本身在技术上是正确的,但它们不是C ++样式的析构函数。
这是我发现的错误,已简化为基本要点...
try { PleaseDisposeMe a = new PleaseDisposeMe(); throw new Exception(); a.Dispose(); } catch (Exception ex) { Log(ex); } // This next call will throw a time-out exception unless the GC // runs a.Dispose in time. PleaseDisposeMe b = new PleaseDisposeMe();
使用FXCop是一个很好的建议,但是如果这是我唯一的答案,那么我的问题就必须成为Cpeople的恳求,或者使用C ++。二十嵌套使用语句的人吗?
解决方案
回答
~ClassName() { }
编辑(粗体):
如果将对象移出范围并由垃圾收集器整理,则将调用if,但是这不是确定性的,不能保证在任何特定时间发生。
这称为终结器。所有带有终结器的对象都会由垃圾收集器放置在特殊的终结队列中,在该队列上将调用finalize方法(因此,从技术上讲,声明空终结器会降低性能)。
public class DisposableFinalisableClass : IDisposable { ~DisposableFinalisableClass() { Dispose(false); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if (disposing) { // tidy managed resources } // tidy unmanaged resources } }
对于框架资源,根据框架准则的"接受"处置方式如下:
回答
因此,上面的意思是,如果有人调用Dispose,则对非托管资源进行整理。但是,如果有人忘记调用Dispose或者阻止Dispose被称为非托管资源的异常,仍然会被整理掉,直到GC收到肮脏的手套(包括应用程序关闭或者意外结束)后才进行整理。 )。
不幸的是,没有任何方法可以直接在代码中直接执行此操作。如果这是内部问题,则可以使用各种代码分析解决方案来解决此类问题。我们是否研究过FxCop?我认为这将解决这些情况,并且在所有IDisposable对象可能悬而未决的情况下都可以使用。如果它是人们在组织外部使用的组件,并且我们不需要FxCop,那么文档实际上是我们唯一的手段:)。
回答
编辑:对于终结器,这并不能真正保证终结器何时发生。因此,这可能是解决方案,但要视情况而定。
这与程序员忘记在C ++中使用delete没什么不同,除了至少在这里,垃圾收集器最终仍会赶上它。
回答
如果我们担心的唯一资源是内存,则无需使用IDisposable。该框架将自行处理。 IDisposable仅适用于非托管资源,例如数据库连接,文件流,套接字等。
If will get called when the object is moved out of scope and is tidied by the garbage collector.
@争吵
该语句具有误导性,而且我读错了它:绝对不能保证何时调用终结器。我们完全正确,billpg应该实现终结器;但是,当对象超出他想要的范围时,将不会自动调用它。证据,"完成操作"下的第一个项目符号有以下限制。
回答
实际上,Microsoft授予Chris Sells以创建.NET的实现,该实现使用引用计数而不是垃圾回收Link。事实证明,这对性能造成了很大的影响。
更好的设计是使此类在释放之前自行释放昂贵的资源。
回答
例如,如果它是数据库连接,则仅在需要时连接并在释放实际类很长时间之前立即释放。
- 每个IDisposable类必须具有终结器
- 每当使用IDisposable对象时,都必须在" using"块内使用它。唯一的例外是,如果对象是另一个类的成员,则包含类必须是IDisposable的,并且必须在其自己的" Dispose"实现中调用该成员的" Dispose"方法。这意味着除了在另一个" Dispose"方法内部,开发人员绝不应调用" Dispose",从而消除了问题中描述的错误。
- 每个终结器中的代码必须以警告/错误日志开头,通知我们已调用终结器。这样,在发布代码之前,我们极有机会发现上述错误,此外,这还可能暗示系统中发生了错误。
在我工作的地方,我们使用以下准则:
为了使我们的生活更轻松,我们在基础架构中还有一个SafeDispose方法,该方法在try-catch块(带有错误日志记录)中调用其参数的Dispose方法,以防万一(尽管Dispose方法不应该引发异常) )。
另请参阅:克里斯·里昂(Chris Lyon)关于IDisposable的建议
编辑:
@Quarrelsome:我们应该做的一件事是在'Dispose'中调用GC.SuppressFinalize,这样,如果对象被处置,则不会被"重新处置"。
class MyDisposable: IDisposable { public void Dispose() { lock(this) { if (disposed) { return; } disposed = true; } GC.SuppressFinalize(this); // Do actual disposing here ... } private bool disposed = false; }
通常也建议持有一个标志,该标志指示对象是否已经被处置。跟随模式通常非常好:
回答
当然,锁定并非总是必要的,但是如果不确定是否在多线程环境中使用类,建议保留它。
最佳实践是在类中使用终结器,并始终使用" using"块。
虽然并没有直接的等效项,但终结器看起来像C析构函数,但行为有所不同。
using (SqlConnection con = new SqlConnection("DB con str") ) using (SqlCommand com = new SqlCommand( con, "sql query") ) { //now code is indented one level //technically we're nested twice }
我们应该嵌套" using"块,这就是为什么Ccode布局默认将它们放在同一行上的原因...
PleaseDisposeMe a; try { a = new PleaseDisposeMe(); throw new Exception(); } catch (Exception ex) { Log(ex); } finally { //this always executes, even with the exception a.Dispose(); }
当我们不使用using
时,我们仍然可以在幕后做它所要做的事情:
段落数量不匹配