1

爪哇

在 Java 中,有一个成语叫做“ Finalizer Guardian ”,它可以防止子类覆盖超类的终结器但忘记调用它。这是来自Effective Java Item7的示例:

// Finalizer Guardian idiom
public class Foo {
  // Sole purpose of this object is to finalize outer Foo object
  private final Object finalizerGuardian = new Object() {
    @Override protected void finalize() throws Throwable {
      ... // Finalize outer Foo object
    }
  };
  ... // Remainder omitted
}

使用这种技术,即使具有终结器的子类不调用超类的终结器,私有对象也会运行超类的终结代码。


C#

但是,在C# in a Nutshell中的“从终结器调用 Dispose ”部分中,有一个这样的示例:

class Test : IDisposable {
  public void Dispose() // NOT virtual {
    Dispose (true);
    GC.SuppressFinalize (this); // Prevent finalizer from running.
  }

  protected virtual void Dispose (bool disposing) {
    if (disposing) {
      // Call Dispose() on other objects owned by this instance.
      // You can reference other finalizable objects here.
      // ...
    }
    // Release unmanaged resources owned by (just) this object.
    // ...
  }

  ˜Test() {
    Dispose (false);
  }
} 

作者还表示:

disposing标志意味着它是从Dispose 方法中“正确”调用的,而不是在终结器中的“最后手段模式”中。这个想法是,当使用disposingset to调用时false,此方法通常不应使用终结器引用其他对象(因为这些对象本身可能已被终结,因此处于不可预测的状态)


问题

但是,当我们回顾 Java 的 Finalizer Guardian 习语时,内部私有的 Guardian 对象实际上是在引用/终结外部对象,而外部对象本身可能具有终结器。它违反了C# in a Nutshell的作者所说的。

我很好奇为什么“在终结器中引用其他可终结对象”在 Java 中是可能的,但在 C# 中却不行。谢谢回答。

4

3 回答 3

3

首先,在 C# 中,不能“忘记”在派生类中调用基类终结器,因为没有语法可以覆盖基类终结器 - 它将始终被调用(类似于构造函数)。

实际上,可以覆盖Dispose并忘记调用基类的版本-在这种情况下,将跳过基类中的终结实现。另一方面,当在 C# 中正确实现可终结类时,如果派生类忘记处置父类,则不会导致致命问题 - 真正管理本机资源的类(如操作系统句柄)应该被密封并因此免受问题的影响(因为可以'在这种情况下不要覆盖任何方法)。

下半年关于敲定订单:

我不知道Java终结如何或是否保证对象以一致的顺序终结,因此所有引用在所有终结器完成之前都是有效的......

在 .Net/C# 终结器中,顺序是未定义的——这意味着在确定需要终结的对象(由于缺少外部引用)之后,该集合中的对象将在没有任何特定顺序的情况下调用终结器。结果,如果集合中的对象相互引用,而不是在调用最后一个对象的终结器时,所有其他对象都已经终结。

于 2014-02-14T06:06:41.223 回答
1

我觉得没那么简单。我认为您可以引用 .net 和 java 中的其他对象,并且同样的问题存在于其他对象处于不可预测的状态。需要研究两者的内部工作才能完全理解它们,但我对java的了解并不深入,我只认为它们非常相似。顺便说一句,这个终结器守护者对我来说看起来很可疑。

于 2014-02-14T06:13:16.537 回答
1

您没有显示终结器监护人的代码,但我建议应该避免让可终结对象保留对原始对象的引用。相反,原始文件中需要清理的那些方面应该封装在可终结的对象中(可能使用AtomicReferenceor AtomicInteger)。例如,封装 OS 文件句柄的对象可以将句柄本身封装在私有可终结对象中,该对象将句柄保存在AtomicInteger. 如果外部对象被遗弃,则可以清理句柄而无需访问外部对象。如果外部对象被要求关闭文件,它可以将请求中继到内部对象,然后内部对象可以读取并清除文件AtomicInteger存储句柄的位置(从而确保文件只能关闭一次)。

于 2014-03-07T20:31:10.927 回答