C# 中的继承和析构函数
根据this,它指出析构函数不能被继承或重载。
就我而言,对于所有子类,析构函数都是相同的。这是否很大程度上告诉我必须在每个子类中定义相同的析构函数。我没有办法在基类中声明析构函数并处理销毁吗?假设我有这样的事情:
class A
{
~A()
{
SomethingA();
}
}
class B : A
{
}
B b = new B();
当 B 被销毁时,它的析构函数不会被调用?
According to this, it states that Destructors cannot be inherited or overloaded.
In my case, for all subclasses, the destructors will be identical. Is this pretty much telling me that I must define the same destructor in each sub class. There is no way that I can declare the destructor in the base class and have the handle the destruction? Say I have something like this:
class A
{
~A()
{
SomethingA();
}
}
class B : A
{
}
B b = new B();
When B
is destroyed, its destructor wont be called?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
正确的。析构函数不是可继承的成员,也不是虚拟的,因此不能被重写。它们始终具有相同的签名,因此不会过载。
您问这样一个基本问题的事实告诉我,您不应该首先实现析构函数。正确实现析构函数是 C# 中最难做的事情之一,除了最琐碎的案件。为什么您认为需要实现析构函数?
不,一点也不。您是如何从析构函数不被继承这一事实得出这个结论的?
当然,如果您一开始就决心实现析构函数,那么这是明智的做法。
这是不正确的。
我认为,亲自尝试比在这里提出问题并等待答复要花费更少的时间。
我之前的推测是正确的。 在您深入了解整个垃圾收集过程之前,您绝对不应该实现析构函数。例如,您认为变量在超出范围时会被收集,这一事实表明您不这么认为足够深入地理解这一点以编写正确的析构函数。
当收集器确定某个对象无法从 gc 根访问,并且该对象具有尚未被抑制的终结器时,则通过将其放置在终结队列中以供终结器线程提供服务,将该对象提升到下一代。如果没有,则回收其内存。
当终结器线程开始运行时,它会运行该对象的所有析构函数。 (析构函数将按从派生最多到最少派生的顺序运行。)在该过程之后,对象可能无法访问,也可能不可达,并且终结可能会或可能不会被抑制。如果确定该对象不可访问,则整个过程将重新开始。
我无法充分强调您需要多么了解 GC 过程才能正确执行此操作。当您编写析构函数时,它会在一个没有任何意义的环境中运行。对象中的所有引用可能都是仅以终结器队列为根的对象;通常所有的参考文献都是关于生物的。引用可能是已经完成的对象。析构函数在不同的线程上运行。即使构造函数失败,析构函数也会运行,因此对象甚至可能无法正确构造。 非原子值类型的字段可能只被部分写入——当线程中止时,双精度字段完全有可能只有构造函数设置的四个字节;终结器将看到该部分写入的字段。即使对象因中止事务而处于不一致状态,析构函数也会运行。等等。编写析构函数时,您必须极度防御。
这个答案也可能有帮助:
我应该何时创建析构函数?
Correct. Destructors are not inheritable members, and are not virtual and so cannot be overridden. They always have the same signature so they cannot be overloaded.
The fact that you are asking such a basic question is telling me that you should not be implementing a destructor in the first place. Implementing a destructor correctly is one of the hardest things to do in C# in all but the most trivial cases. Why do you believe that you need to implement a destructor?
No, not at all. How did you arrive at that conclusion from the fact that destructors are not inherited?
Sure, that's a sensible thing to do, provided that you're bent on implementing a destructor in the first place.
That is incorrect.
It occurs to me that it would have taken you a lot less time to try it yourself than to ask the question here and wait for a response.
My earlier conjecture is correct. You definitely should not be implementing a destructor until you deeply understand the entire garbage collection process. The fact that you believe that variables are collected when they fall out of scope, for example, indicates that you don't understand this deeply enough to write a correct destructor.
When an object is determined to be unreachable from a gc root by the collector, and the object has a finalizer that has not been suppressed then the object is promoted to the next generation by placing it on the finalization queue for servicing by the finalizer thread. If not, its memory is reclaimed.
When the finalizer thread gets around to running, it runs all the destructors of the object. (Destructors will run in order from most derived to least derived.) After that process the object then may or may not be unreachable and finalization may or may not be suppressed. If the object is determined to be unreachable then the whole process starts again.
I cannot emphasize enough how well you need to understand the GC process in order to do this correctly. When you write a destructor it runs in an environment where nothing makes sense. All the references in the object might be to objects that are only rooted by the finalizer queue; normally all references are to live things. References might be to objects that are already finalized. Destructors run on a different thread. Destructors run even if the constructor failed, so the object might not even be constructed properly. Fields of non-atomic value types may be only partially written -- it is entirely possible for a double field to have only four of its bytes set by the constructor when the thread is aborted; the finalizer will see that partially-written field. Destructors run even if the object was placed in an inconsistent state by an aborted transaction. And so on. You have to be extremely defensive when writing a destructor.
This answer might also help:
When should I create a destructor?
它不是 C# 中的析构函数。它被称为终结器;并且何时调用它是不确定的。实际上你根本不能指望它会被调用。
终结器被用作清理非托管资源的最后手段。您应该查看处置模式。
It's not a destructor in C#. It's known as a Finializer; and when it is called is non-deterministic. You actually can't count on it to be called at all.
Finalizers are used as a last resort to clean up unmanaged resources. You should look into the Dispose pattern.
当 B 的实例被销毁时,您在 A 中定义的终结器将被调用。
如果在 A 和 B 中都定义了终结器,则最具体的终结器 (B) 将首先运行,然后是最不具体的终结器 (A)。
The finalizer you defined in A will be called when an instance of B is destroyed.
If you define a finalizer in both A and B, the most specific finalizer (B) will run first, then the least specific (A).
我从事 .NET 编程已有近十年了。 唯一一次我实现了终结器,它最终成为内存泄漏的原因,而不是其他原因。你几乎永远不需要它们。
I've been doing .NET programming for close to a decade. The only time I implemented a finalizer, it ended up being a cause for a memory leak and nothing else. You almost never need them.
一个快速的控制台应用程序可以帮助测试此类事情。
输出可能是...
A quick console app can help test this sort of thing.
The output might be...
如果您为 B 定义了析构函数,则会调用它,然后调用 A 的析构函数。请参阅您提供的链接底部的示例。
If you define a destructor for B, it will be called, followed by A's. See the example at the bottom of the link you provided.
好吧,我不知道析构函数,但是您还有其他有用的清理方法,例如 IDisposable 中的 Finalize() 和 Dispose()。
Well, I don't know about destructors, but you have other useful methods for cleanup like Finalize() and Dispose() from IDisposable.