你应该实现IDisposable.Dispose(),以便它永远不会抛出?

54 .net c# idisposable

对于C++(析构函数)中的等效机制,建议通常不应抛出任何异常.这主要是因为这样做可能会终止您的流程,这很少是一个好策略.

在.NET的等效场景中......

  1. 抛出第一个异常
  2. 作为第一个异常的结果,执行finally块
  3. finally块调用Dispose()方法
  4. Dispose()方法抛出第二个异常

...您的流程不会立即终止.但是,由于.NET无法用第二个异常替换第一个异常,因此会丢失信息.因此,调用堆栈上某处的catch块将永远不会出现第一个异常.然而,人们通常对第一个例外更感兴趣,因为这通常会提供更好的线索,说明为什么事情开始出错.

由于.NET缺少一种机制来检测代码是否在异常处于挂起状态时被执行,因此似乎只有两种选择可以实现IDisposable:

  • 始终吞下Dispose()中发生的所有异常.不好,因为你可能最终吞下OutOfMemoryException,ExecutionEngineException等等,我通常宁愿在它们发生时拆除它而没有另外的异常已经挂起.
  • 让所有异常传播出Dispose().不好,因为您可能会丢失有关问题根本原因的信息,请参阅上文.

那么,两个邪恶中哪一个较小?有没有更好的办法?

编辑:为了澄清,我不是在谈论积极抛出Dispose()或不抛出异常,我说的是让Dispose()调用的方法抛出的异常传播出Dispose()或不传播,例如:

using System;
using System.Net.Sockets;

public sealed class NntpClient : IDisposable
{
    private TcpClient tcpClient;

    public NntpClient(string hostname, int port)
    {
        this.tcpClient = new TcpClient(hostname, port);
    }

    public void Dispose()
    {
        // Should we implement like this or leave away the try-catch?
        try
        {
            this.tcpClient.Close(); // Let's assume that this might throw
        }
        catch
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ric*_*ard 36

框架设计指南(2 ED)有这样的(§9.4.1):

避免在Dispose(bool)中抛出异常,除非在包含进程已被破坏的临界情况下(泄漏,不一致的共享状态等).

评论[编辑]:

  • 有指导方针,而不是硬性规则.这是一个"避免"而不是"不要"的指导方针.如上所述(在评论中)框架打破了这个(和其他)指导方针.诀窍是知道何时打破指南.在许多方面,这是一个熟练工和大师之间的区别.
  • 如果清理的某些部分可能失败,那么应该提供一个Close方法,该方法将抛出异常,以便调用者可以处理它们.
  • 如果您正在遵循dispose模式(如果类型直接包含某些非托管资源,则应该是这样),那么Dispose(bool)可以从终结器调用,从终结器中抛出是一个坏主意,并将阻止其他对象被最终确定.

我的观点:从Dispose中逃避的异常应该只是那些,如在指南中那样,充分的灾难性,以至于当前的过程不可能有进一步的可靠功能.


Mar*_*ell 17

我认为吞咽是这种情况下两种邪恶中较小的一种,因为最好提出原始 Exception警告:除非,或许未能干净地处置本身是非常关键的(也许如果一个TransactionScope无法处置,因为这可能表示回滚失败).

有关方面的更多想法,请参阅此处 - 包括包装/扩展方法的想法:

using(var foo = GetDodgyDisposableObject().Wrap()) {
   foo.BaseObject.SomeMethod();
   foo.BaseObject.SomeOtherMethod(); // etc
} // now exits properly even if Dispose() throws
Run Code Online (Sandbox Code Playgroud)

当然,你也可以做一些奇怪的事情,你用原始和第二(Dispose())异常重新抛出一个复合异常 - 但是想一想:你可能有多个using块......它很快就会变得无法管理.实际上,最初的例外是有趣的.

  • 我同意它可能变得难以管理,但重要的一点是,原始异常是有趣的*除非*没有原始异常。我继续写下了这个奇怪的地方,并且对此感到非常满意,尽管在我的特殊情况下,我最终只是吞下了 *within* Dispose 中的异常(因为我可以修改 = 代码并且不想访问每个用户班上) (2认同)

Meh*_*ari 6

Dispose应该设计为实现其目的,处理对象.此任务是安全的,并且在大多数情况下不会抛出异常.如果你看到自己抛出异常Dispose,你应该三思而后行,看看你是否在做太多的东西.除此之外,我认为Dispose应该像所有其他方法一样对待:处理如果你可以用它做某事,如果你做不到就让它冒泡.

编辑:对于指定的示例,我会编写代码,以便我的代码不会导致异常,但清除TcpClientup可能会导致异常,这在我看来应该有效传播(或者作为更通用的处理和重新抛出异常,就像任何方法一样):

public void Dispose() { 
   if (tcpClient != null)
     tcpClient.Close();
}
Run Code Online (Sandbox Code Playgroud)

但是,就像任何方法一样,如果你知道tcpClient.Close()可能会抛出一个应该被忽略的异常(无关紧要)或者应该被另一个异常对象表示,你可能想要捕获它.