析构函数执行顺序?

Destructor execution order?

我知道c#中的析构函数没有执行顺序

我在几个 类 中使用了以下结构,它用于破坏实例和静态信息:

public class MyClass
{
    private static readonly Destructor DestructorObject = new Destructor();

    ~MyClass()
    {
        Console.WriteLine("Destructor Called");
    }

    static void Main(string[] args)
    {
        var myClass = new MyClass();
    }

    private sealed class Destructor
    {
        ~Destructor()
        {
            Console.WriteLine("Static Destructor Called");
        }
    }
}

正如我上面提到的,析构函数的顺序是未定义的。但是当我在许多 classes 中使用这个构造时,我发现,在每个 class 中都有一个不变的顺序,即使我正在重新编译应用程序和 运行再来一次。

意味着 MyClass1 可以总是 运行 ~MyClass1 而另一个 class MyClass2 可以总是 运行 ~Destructor第一.

明明每个class都有一个"hidden"订单,我能相信吗?

As there clearly is a "hidden" order for each class, can I trust it?

不,你不能。如果你看看 the docs,他们尖叫:

The finalizers of two objects are not guaranteed to run in any specific order, even if one object refers to the other. That is, if Object A has a reference to Object B and both have finalizers, Object B might have already been finalized when the finalizer of Object A starts.

依赖这样的实现细节作为正常执行流程的一部分将是一个非常糟糕的主意。

看到出于某种原因您选择使用终结器作为清理静态资源的方式,您应该首先考虑这是否是正确的方法,taking into account everything destructors imply,然后 至少 实施 IDisposable 并让调用者有机会处理资源,同时调用 GC.SupressFinalize

在你的对象中使用这个作为一种常见的方法也会导致对象延长它们的生命,因为它们只有在移动到 f-reachable 队列后才有资格被收集,并且依赖于终结器线程来实际清理它们,这根本无法保证。

Eric Lippert 最近 (18-05-2015) 开始了一个名为 When everything you know is wrong 的系列讨论终结器神话,我建议你看一看。

编辑:

有趣,Erics second post(今天发布)在系列中回答了这个问题:

Myth: Finalizers run in a predictable order

Suppose we have a tree of objects, all finalizable, and all on the finalizer queue. There is no requirement whatsoever that the tree be finalized from the root to the leaves, from the leaves to the root, or any other order.

我建议阅读 Eric Lippert's latest blog post,但由于未定义顺序,您不能指望在未来的版本中保持相同的顺序。

正如之前多次尖叫的那样,终结器不保证任何顺序。你不能做任何假设。您应该假设最坏的情况(即)它可以按任何顺序执行。

As there clearly is a "hidden" order for each class, can I trust it?

是也不是。

对于普通对象,终结器的执行顺序是不可预测的。所以不行。你不能依赖其中的任何东西。

对于从 SafeHandle 继承的对象,是的,有一些顺序。例如:如果你有两个准备终结的对象,一个是从SafeHandle派生的,另一个不是,那么保证不从SafeHandle继承的对象的终结器会先执行执行 SafeHandle 的终结器。

存在此排序的原因在 this blog post 中有所描述。这没有记录,但由 BCL 团队发布在博客上。将来不太可能更改。但是...

为了证明这一点,下面的程序总是会先完成 Destructor class 然后 MyClass。因为你知道 MyClass 继承自 SafeHandle.

public class MyClass : SafeHandle
{
    private static readonly Destructor DestructorObject = new Destructor();

    ~MyClass()
    {
        Console.WriteLine("Destructor Called");
    }

    protected override bool ReleaseHandle()
    {
        return true;
    }

    public override bool IsInvalid
    {
        get { return false; }
    }

    static void Main(string[] args)
    {
        var myClass = new MyClass(IntPtr.Zero, true);
    }

    private sealed class Destructor
    {
        ~Destructor()
        {
            Console.WriteLine("Static Destructor Called");
        }
    }

    public MyClass(IntPtr invalidHandleValue, bool ownsHandle)
        : base(invalidHandleValue, ownsHandle)
    {
    }
}