【问题标题】:How do I handle all the exceptions in a C# class where both ctor and finalizer throw exceptions?如何处理 C# 类中 ctor 和 finalizer 都抛出异常的所有异常?
【发布时间】:2010-04-20 01:17:39
【问题描述】:

在某些情况下,如何处理类似于以下类的所有异常?

class Test : IDisposable {
  public Test() {
    throw new Exception("Exception in ctor");  
  }
  public void Dispose() {
    throw new Exception("Exception in Dispose()");
  }
  ~Test() {
    this.Dispose();
  }
}

我试过了,但它不起作用:

static void Main() {
  Test t = null;
  try {
    t = new Test();
  }
  catch (Exception ex) {
    Console.Error.WriteLine(ex.Message);
  }

  // t is still null
}

我也尝试过使用“使用”,但它不能处理从 ~Test() 抛出的异常;

static void Main() {
  try {
    using (Test t = new Test()) { }
  }
  catch (Exception ex) {
    Console.Error.WriteLine(ex.Message);
  }
}

有什么想法可以解决吗?

【问题讨论】:

  • 在第二种情况下(不使用),您从未真正调用过 Dispose()。 C# 是垃圾收集器,因此在 GC 决定是时候清理之前,实际上不会清理该类。 GC 也不会调用 Dispose(即使您定义了它),因此您可以在使用 IDisposable 类时使用“使用”或显式调用 Dispose。
  • 感谢您的回答。事实上,Test 类不是我编写的,它只是为了演示目的,显示了一个我无法控制的具有类似行为的类。关键是,虽然构造函数抛出异常,但Test的对象是没有引用变量的半积。

标签: c# exception-handling finalizer


【解决方案1】:

首先,终结器应该从不抛出异常。如果是这样,则说明发生了灾难性的错误,应用程序应该会严重崩溃。终结器也不应该直接调用 Dispose()。终结器仅用于释放非托管资源,因为一旦终结器运行,托管资源甚至可能不处于有效状态。垃圾收集器已经清理了托管引用,因此您只需在 Dispose 中处理它们,而不是在 Finalizer 中。

也就是说,如果您显式调用 Dispose,则应捕获 Dispose 中的异常。我不太了解“使用”案例如何没有引发异常。也就是说,如果可以避免的话,Dispose 也不应该抛出异常。特别是,在 using 块之后引发异常的 Dispose 将使用 Dispose 异常“覆盖”在 using 块内可能发生的任何异常。


一些额外的参考资料here

【讨论】:

  • 问题是如果构造函数抛出异常,我没有指向“部分”对象的指针,因此我不能显式调用 Dispose()。此外,使用“using”语句会捕获构造函数的异常 - 但是当 GC 调用终结器时,会出现另一个无法处理的异常。
  • 如果一个对象在分配了部分非托管资源后在其构造函数中抛出了异常,那么这是该对象的设计缺陷。不幸的是,您对此无能为力。如果一个对象在构造过程中必须抛出异常,如果可能的话,更好的设计是“回滚”非托管分配。更好的是,将可能失败的逻辑从构造函数移到其他 Init 方法。
【解决方案2】:

我认为部分答案是在这些情况下您不应该处理异常。

只有在可以从异常中恢复,或者可以向异常添加额外信息并重新抛出时,才应该捕获异常。您不应该捕获所有异常。让调用堆栈中较高的代码处理尽可能多的异常。

【讨论】:

    【解决方案3】:

    我有几点意见。

    首先,避免从 Dispose 抛出异常。事实上,我几乎会说永远不会。 .NET 开发人员已经习惯于期望 Dispose 总是会成功并且有充分的理由。每次都将调用包装在 try-catch 中会很尴尬,而且肯定会降低可读性。

    其次,这是一个经常争论的问题,避免从构造函数中抛出异常。与状态验证相关的异常,例如 ArgumentException 或 IndexOutOfRangeException 是可以的,因为它们通常是由于违反前置条件而生成的,并且通常是编程错误的结果。但是,诸如 SqlException 之类的不可预测的异常几乎会迫使调用者将构造函数包装在 try-catch 块中。同样,这会导致尴尬的编码场景。但是,更重要的是,在构造函数分配非托管资源然后在将新实例返回给调用者之前抛出异常的情况下,这可能会导致细微的资源泄漏。如果没有实例引用,调用者将无法调用 Dispose。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-28
      • 2013-07-24
      • 1970-01-01
      • 1970-01-01
      • 2011-12-12
      • 2011-10-10
      • 2013-03-23
      相关资源
      最近更新 更多