抽象类的析构函数

6

在C++中,我知道当我们想要从一个基类继承时,通常应该使用虚析构函数。然而,在C#中我不确定应该做什么。考虑以下代码:

public abstract class Character
{
    private string characterName;
    public int health;

    Character()
    {

    }

    ~Character(){

    }

    public virtual void SetCharacterName( string tempName )
    {
        characterName = tempName;
    }

    public virtual string GetCharacterName( )
    {
        return characterName;
    }
}

(注:我听说Unity3D对C#的实现略有不同。也许可以忽略一些小的格式错误,代码似乎是可行的……)

我的第一反应是将~Character()析构函数定义为虚函数,如下所示:

virtual ~Character(){

}

然而这样做会导致IDE返回错误。

在C#中,对于希望被继承的抽象类,是否有必要或者被认为是标准使用虚析构函数?还是说有其他方法可以使用C#实现虚析构函数?


集成开发环境(IDE)返回了什么错误? - Tim
1
我希望你知道C++为什么有“虚析构函数”的原因。这与在C++代码中手动销毁对象以及指针类型和由此引发的问题有关。在C#/.NET中,您不需要手动销毁对象(运行时的垃圾回收器会处理它,并且还会按正确顺序调用继承链中的析构函数)。这意味着,在C++中使虚拟析构函数必要的原因在.NET中不存在--因此.NET中没有虚拟析构函数。 - user2819245
@elgonzo - 多重继承不是虚析构函数的主要原因吗?(如果没有它,只需像在C#中一样自动调用基类即可)。 - Alexei Levenkov
1
@AlexeiLevenkov,可能与多重继承有关。但主要原因是多态性。请参见此处:https://dev59.com/IHRB5IYBdhLWcg3w77on?answertab=active#tab-top - user2819245
回答Tim的问题,以防其他人也在想,它说Virtual关键字不允许出现在~Character()之前。 - TheYokai
2个回答

11

C#没有确定性析构函数,实际上,它并没有真正意义上的析构函数,而是有终结器和IDisposable

如果垃圾回收器在回收对象实例时得到机会,它会将它们清理掉。所有对象最终都会以一种或另一种方式被清理,当应用程序域终止时也是如此,但可能某个给定的对象可能会在应用程序域的持续时间内保留下来。在您的情况下,您不需要做任何事情,因为您的对象没有需要清理的资源。当垃圾回收器扫描并清除未引用的对象时,它们会得到正确处置。

大多数情况下,人们不需要终结器。您可能想要阅读有关终结器的信息,请参阅http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx,注意以下内容:

确定终结器执行的确切时间是未定义的。要确保对类的实例的资源释放具有确定性,请实现Close方法或提供IDisposable.Dispose实现。

您还可以阅读了解何时在您的.NET类中使用终结器,并注意以下内容:

为什么Finalize方法不好?

...

如果一个对象有一个终结器,它将被放置在FinalizationQueue中,并受到一些额外的清理。一旦该对象不再从线程或全局引用中引用,下次GC运行时,它将看到该对象已准备好进行收集。但是它现在不能对其进行收集。它必须先让终结器运行。因此,GC将完成收集,然后终结器将最后处理对象,接着会再次进行GC收集。

这可能会极大地影响性能,因为您应记住,所有托管线程都将停止等待GC,然后GC将停止等待终结器线程。

关于终结的更多数据,请参阅尽可能多的信息,以便您以最佳方式使用它。

如果您的对象需要确定性,则可以实现IDisposable并显式调用Dispose()或使用using块:

using ( Character foo = CharacterFactory.CreateInstance("Jane") )
{
   // do something useful with Jane
}

当包括using块退出时,保证会调用foo.Dispose()。这与此代码相同(除了foo的范围):

Character foo = ... ;
try
{
  ...
}
finally
{
  foo.Dispose() ;
}

然而,IDisposable 的重点更在于确保非托管资源能够及时释放。当您的对象超出作用域并挂起等待垃圾回收时,会阻塞400个用户并使DBA感到烦躁。或者当您的对象超出作用域时,会留下一个独占锁定的文件打开。


1
只是挑刺一下,但C#有析构函数。看看当前的C# 5.0规范:第1.6.7.6章是关于析构函数的。我认为,在更一般的.NET术语中,它被称为“终结器”——C#编译器将析构函数转换为终结器。就像我说的,我只是在挑刺而已 ;) - user2819245
1
@elgonzo:析构函数是“确定性”的;终结器则不是。CLI规范使用了恰当的术语;而C#规范却没有。 Eric Lippert在这里提出假设 :“语言设计委员会希望能够保留一个可能性,即 C# "析构函数" 可以被实现为 CLR 终结器之外的其他东西。也就是说,“析构函数”被设计成是一个C#语言概念,并不一定与CLR的“终结器”概念一一对应。” - Nicholas Carey
很高兴知道这些特定术语所关联的语义差异。这是Lippert先生的一篇有趣的文章,感谢提供链接 :) - user2819245

7
在C#中,对于希望被继承的抽象类,使用虚析构函数是否必要或被视为标准?
通常情况下,在C#中不需要使用任何析构函数。这些函数可能会被垃圾回收器在任何时候(或不)调用。
当您需要确定性地清理资源时,请实现IDisposable接口,并使用using进行清理(例如非托管资源)。
析构函数不能被继承或重载。相关的stackoverflow线程详细解释了此问题:Inheritance and Destructors in C#

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