【问题标题】:Inheritance and Destructors in C#C#中的继承和析构函数
【发布时间】:2011-12-31 17:46:18
【问题描述】:

根据this,它声明Destructors cannot be inherited or overloaded. 在我的例子中,对于所有子类,析构函数都是相同的。这是否几乎告诉我我必须在每个子类中定义相同的析构函数。有没有办法在基类中声明析构函数并处理销毁?假设我有这样的事情:

class A
{
    ~A()
    {
        SomethingA();
    }

}

class B : A
{

}

B b = new B();

B被销毁时,它的析构函数不会被调用?

【问题讨论】:

  • 似乎很容易测试。您是否有未运行 b 的析构函数的示例?
  • @Yuck 好吧,这可能让我想到另一个愚蠢的问题......“什么时候真正调用析构函数?是在垃圾收集时,当变量超出范围时?
  • 这是一个不同的问题。我鼓励你这样发布它,但它已经被如此频繁地询问了......长话短说:关于垃圾收集,但这可能会在很多之后发生或根本不会发生.
  • 最好阅读析构函数以及为什么使用它们。我怀疑你根本不需要。

标签: c# inheritance destructor


【解决方案1】:

当 B 的实例被销毁时,将调用您在 A 中定义的终结器。

如果您在 A 和 B 中都定义了一个终结器,那么最具体的终结器 (B) 将首先运行,然后是最不具体的 (A)。

【讨论】:

    【解决方案2】:

    如果你为 B 定义了一个析构函数,它将被调用,然后是 A。请参阅example at the bottom of the link you provided

    【讨论】:

      【解决方案3】:

      它不是 C# 中的析构函数。它被称为终结器;并且何时调用它是不确定的。你实际上根本不能指望它被调用。

      终结器被用作清理非托管资源的最后手段。您应该查看Dispose pattern

      【讨论】:

      • 我赞同在大多数情况下使用 IDisposable 的想法
      • 从技术上讲,C# 规范将其称为析构函数。 (我个人认为这是一个误导性的名称,即使在 C# 规范中也应该将其称为终结器)
      • 甚至 Eric Lippert laments 在规范中都将其称为析构函数。
      • @CodeInChaos:我同意,但我们现在坚持下去了。
      • 声明“它不是 C# 中的析构函数”没有帮助。
      【解决方案4】:

      据此,它声明析构函数不能被继承或重载。

      正确。析构函数不是可继承的成员,也不是虚拟的,因此不能被覆盖。它们始终具有相同的签名,因此它们不会被重载。

      就我而言,对于所有子类,析构函数都是相同的。

      您提出这样一个基本问题的事实告诉我,您不应该首先实现析构函数。正确实现析构函数是 C# 中最难做的事情之一除了最微不足道的情况外。为什么你认为你需要实现析构函数?

      这是在告诉我必须在每个子类中定义相同的析构函数吗?

      不,一点也不。您是如何从析构函数不被继承这一事实得出这个结论的?

      我没有办法在基类中声明析构函数并让处理销毁?

      当然,这是明智之举,前提是您一开始就打算实现析构函数。

      当B被销毁时,它的析构函数不会被调用?

      这是不正确的。

      在我看来,与在这里提出问题并等待回复相比,您自己尝试所花费的时间要少得多。

      什么时候真正调用析构函数?当变量超出范围时,它是在垃圾收集上吗?

      我之前的猜想是正确的。 在您深入了解整个垃圾收集过程之前,您绝对不应该实现析构函数。例如,您认为变量超出范围时会被收集这一事实表明您没有足够深入地理解这一点以编写正确的析构函数。

      当一个对象被收集器确定为无法从 gc 根访问,并且该对象有一个没有被抑制的终结器时,通过将对象放在终结队列中以供终结器线程。如果不是,则回收其内存。

      当终结器线程开始运行时,它会运行对象的所有析构函数。 (析构函数将按照从最多派生到最少派生的顺序运行。)在该过程之后,对象可能无法访问,也可能无法访问,最终确定可能会或可能不会被抑制。如果确定对象不可访问,则整个过程重新开始。

      为了正确执行此操作,您需要充分了解 GC 过程,我怎么强调都不为过。当您编写析构函数时,它会在没有任何意义的环境中运行。对象中的所有引用都可能指向仅以终结器队列为根的对象;通常所有的参考都是活的东西。引用可能是对已经完成的对象。析构函数在不同的线程上运行。即使构造函数失败,析构函数也会运行,因此对象甚至可能无法正确构造非原子值类型的字段只能部分写入——当线程中止时,双字段完全有可能只有四个字节由构造函数设置;终结器将看到部分写入的字段。即使对象被中止的事务置于不一致的状态,析构函数也会运行。等等。在编写析构函数时,您必须极度防御

      这个答案也可能有帮助:

      When should I create a destructor?

      【讨论】:

      • 你是什么意思“引用可能是死的东西”。所有对象引用都保证指向有效的 .net 对象;如果有直接或间接引用的任何对象覆盖 Finalize 并且没有抑制终结,则它们可能已经运行或计划运行,但只要一个对象注册为终结,它和任何对象都不会它持有的直接或间接引用可以从内存中删除。终结的最大困难可能是未知的线程上下文。
      • 一个对象不应该对代码不控制的可终结对象的状态做出任何假设,但是如果 Class1 和 Class2 都在同一个程序集中,并且 Object1(Class1 类型)持有一个引用到 Object2(属于 Class2)并在其终结器中访问它,它必须为 Object2 可能已经终结或可能正在等待终结的可能性做好准备。如果对 Object2 的任何引用已经暴露在外部,它还必须考虑到对 Object2 的外部实时引用可能仍然存在的可能性。然而,受制于这些限制,...
      • ...对象拥有相互协调的终结器是完全可能和合理的。我花了一段时间才理解 GC 循环如何将对象划分为具有终结机制以外的根的对象、已注册终结但没有 其他 根的对象以及没有根引用的对象完全没有,并且要理解终结不会以任何方式干扰对象,除了调用终结(它只会以明确编程的方式干扰对象)。
      • 嘿,埃里克,我已将您的回答标记为粗鲁,如果您再读一遍,我想您会同意的。也许你今天过得很糟糕? :) OP 似乎来自 C++,这将使这个问题非常合理。
      • @Seth:我支持答案。我从来没有说过这个问题是不合理的。答案准确而有帮助。
      【解决方案5】:

      好吧,我不知道析构函数,但是您还有其他有用的清理方法,例如 IDisposable 中的 Finalize() 和 Dispose()。

      【讨论】:

      • C# 析构函数和Finalize 几乎是等价的。
      • 是的,析构函数隐式调用了Finalize(),但由于这是一个对象的方法,它相对于继承/覆盖的行为更加清晰。
      • @Tudor:不幸的是,C# 的制造者决定,如果允许,人们可能会覆盖 Object.Finalize() 而不是使用漂亮的 C# 析构函数语法,因此有必要禁止覆盖 @ 987654323@。对我来说似乎很愚蠢,但我没有编写语言规范。
      【解决方案6】:

      我从事 .NET 编程已经有近十年的时间了。 唯一次我实现了终结器,它最终成为内存泄漏的原因,仅此而已。你几乎从不需要它们。

      【讨论】:

      • 我发现它们偶尔对软件的调试版本很有帮助。例如,~MyClass() {if (this.Oops) Debug.Fail("Oops");}
      • 这不是问题的答案。仅仅因为您可能一直从事的项目通常没有非托管资源,并不意味着每个使用 .Net 的人都是如此。我同意绝大多数 C# 对象不需要终结器,但仍有许多情况需要它们来防止资源泄漏(或死锁等),当意外发生阻止对象被正常处置时。
      【解决方案7】:

      一个快速的控制台应用可以帮助测试这类事情。

      using System;
      
      class A
      {
          ~A() => Console.WriteLine("~A");       
      }
      
      class B : A
      {
          ~B() => Console.WriteLine("~B");
      }
      
      public class Program
      {
          public static void Main() => new B();        
      }
      

      输出可能是...

      ~B
      ~A
      

      【讨论】:

        猜你喜欢
        • 2011-10-14
        • 2012-11-14
        • 2012-04-28
        • 2021-04-13
        • 1970-01-01
        • 2011-12-27
        • 1970-01-01
        • 2011-11-12
        • 2023-03-30
        相关资源
        最近更新 更多