为什么Finalize方法不允许被覆盖重写?

5

我是 .net 新手,对 C# 中的析构函数机制感到困惑,请解释一下。

在 C# 中,CLR 将析构函数转换为 finalize 方法。如果我们尝试覆盖它(而不是使用析构函数),会出现错误 Error 2 Do not override object.Finalize. Instead, provide a destructor.。

但是,看起来 mscorlib.dll 中的 Object 类实现已将 finalize 定义为 protected override void Finalize(){},那么为什么我们不能覆盖它,这是虚函数的作用。

为什么设计成这样,是为了与 C++ 的析构函数概念保持一致吗。

另外,当我们查看 object 类的定义时,没有提到 finalize 方法,那么 hmscorlib.dll 的定义如何显示 finalize 函数呢?这是否意味着默认的析构函数被转换为 finalize 方法。

public class Object
{     

    public Object();
    public virtual bool Equals(object obj);        
    public static bool Equals(object objA, object objB);       
    public virtual int GetHashCode();       
    public Type GetType();      
    protected object MemberwiseClone();  
    public static bool ReferenceEquals(object objA, object objB);
    public virtual string ToString();
}
5个回答

21
我刚开始学习.NET,对C#中的析构函数机制感到困惑,请解释一下。
在C#中,析构函数被CLR转换成finalize方法。
如果我们尝试覆盖它(不使用析构函数),将会得到一个错误提示:“不要重写object.Finalize(),而是提供一个析构函数。”
mscorlib.dll中Object类的实现定义finalize为protected override void Finalize(){}。
那么为什么我们不能重写它呢?这不是虚函数的作用吗?
因为那样会有两种方式来编写析构函数,这会很混乱。我们希望只有一种方式来编写析构函数。
为什么设计是这样的呢?是为了与C++的析构函数保持一致吗?
是的,这是其中一个原因。还有其他原因,例如通过逻辑上区分“析构函数”和“覆盖finalize方法”的概念,可以使我们在其他环境中使用其他机制实现析构函数。这可能会发生在未来的某个时候,比如我们编写了一个专门用于构建设备驱动程序的C#版本,在这个环境中,垃圾回收的语义可能更像C++析构函数而不是GC终结器。
  • 终结器是非常特殊的方法。你不能像调用普通方法一样调用它们;只有垃圾回收器才能调用它们。它们有不同的异常处理规则。必须确保在派生类终结器之后严格调用基类终结器。它们非常特殊,以至于将它们作为任何其他方法暴露似乎很危险。如果你有一个空置的方法可以调用,可以随意放任,等等,那么很容易忘记终结器的特殊性质。特殊的语法强调了这是特殊代码。

  • 同时,当我们查看对象类的定义时,没有提到finalize方法,那么mscorlib.dll定义如何显示finalize函数。

    假设我们在对象浏览器中展示了一个叫做MagicUnicorn的方法,如果你尝试调用或覆盖它,你会得到一个错误提示“别这么做!”那么展示无法执行任何操作的MagicUnicorn方法有什么意义呢?那只是无用的噪音。

    现在,如果你反汇编mscorlib,那么当然真的会有特殊的Finalize方法。

    这是否意味着默认的析构函数被转换为finalize方法?

    是的。


    代码中有没有任何方法可以在不硬编码GC.SuppressFinalize()GC.KeepAlive()名称的情况下正确地使用析构器?如果C#提供了调用此类函数的语法(而无需使用特定于框架的名称),则我可能会看到析构器语法的某些价值。另一方面,如果编写一个适当的自清理对象需要使用至少两个特定于框架的GC.*方法,那么小部分谜题是特定于语言的而不是特定于框架的价值是什么,我不确定。我错过了什么吗? - supercat

    4

    “为什么会这样呢?”
    这是出于设计决策。可能是为了与C ++相匹配,但这并不重要。

    hmscorlib.dll的定义如何显示finalize函数

    很可能C#编译器进行了特殊干预。同样,这没有实际意义。

    如果您真的需要finalizer,请编写析构函数。更重要的是:您几乎永远不需要使用。

    为了看到这个选择有多么随意:在VB中,您可以override Finalize。而且这两种语言可以很愉快地使用(和继承)彼此的类型。


    3
    “+1 for "you almost never need to".” 表示赞同“你几乎不需要”,意思是很少需要做某件事情。 “IDisposable” 接口通常是必需的,意思是需要使用 IDisposable 接口。 - Richard Szalay
    @Richard,是的,IDisposable类通常但并不总是有一个析构函数。没有IDisposable而有析构函数非常罕见(甚至可疑)。 - H H

    0
    要覆盖Finalize(),您必须使用终结器语法。CLI在内部为某些事物使用固定名称,例如所有精确命名为.ctor的实例构造函数以及所有静态构造函数命名为.cctor。当C#编译器编写输出程序集时,它会确保使用适当的名称来发出项目,这包括将析构函数写为具有名称Finalize()而不是~T()。其他.NET语言的编译器也必须遵循这些规则以符合其自己的语义。
    class A
    {
        ~A() { /* ... */ }
    }
    

    关于终结器的何时和如何使用,请查看其他与终结器和IDisposable有关的问题。


    1
    我认为他们理解了,但是他们想知道为什么的解释。 - Richard JP Le Guen

    0
    在C++中,你需要将析构函数设置为虚函数。原因是这样它才会被调用。
    A a = new B() //where B derived from A
    delete a;
    

    对于C++有点生疏,如果我错了,请原谅。但是请注意,delete传递的是可能是基类的引用。

    在C#中,您不会调用终结器。它由垃圾回收器在内部调用。垃圾回收器不会关心先前分配了什么类型的引用,直接处理对象 :)

    现在,终结器只是从C++中获取语法。因此会产生混淆。

    其他人所说的是,通常不需要终结器。这就是为什么。

    如果您实现了IDisposable,则可以利用“using”语句来决定变量的范围,并在需要时将其处理掉。使用终结器时,方法何时被调用的决定由GC做出。这意味着,如果您需要释放资源,则时机可能不正确。

    希望这可以帮助到您。


    0
    这是否意味着默认析构函数被转换为 finalize 方法?
    是的,C#中的默认(也是唯一的)析构函数语法会编译成 Finalize 方法。这可能是为了给 C++ 开发者提供熟悉的语法。请参阅 MSDN 上的 Destructors 获取更多信息。

    1
    C# 中没有默认的析构函数。 - Anton Tykhyy

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接