析构函数限制 - 从析构函数访问托管成员

Destructor restrictions - access managed member from destructor

在 C# 析构函数(Finalizer)中您不能访问 class 的托管成员,对吗?如果是真的,为什么会这样? 您还知道哪些其他 C# 终结器限制?

示例:

class MyClass
{
    private FileStream _fs;
    private IntPtr _handle;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~MyClass()
    {
        Dispose(false);
    }

    private void Dispose(bool isDisposing)
    {
        if (isDisposing)
        {
            _fs.Dispose(); // Won't be accessed from destructor
        }

        //some way to release '_handle' - this happans anyway (Called by Dispose or by Destructor)
    }
}

你可以。但这有什么意义呢?您所在的对象已变得无法访问,因此所有这些托管资源都已被 GC.So 收集,无需对它们调用 dispose。

来自 msdn

Using Destructors to Release Resources: In general, C# does not require as much memory management as is needed when you develop with a language that does not target a runtime with garbage collection. This is because the .NET Framework garbage collector implicitly manages the allocation and release of memory for your objects. However, when your application encapsulates unmanaged resources such as windows, files, and network connections, you should use destructors to free those resources. When the object is eligible for destruction, the garbage collector runs the Finalize method of the object.

Explicit Release of Resources If your application is using an expensive external resource, we also recommend that you provide a way to explicitly release the resource before the garbage collector frees the object. You do this by implementing a Dispose method from the IDisposable interface that performs the necessary cleanup for the object. This can considerably improve the performance of the application. Even with this explicit control over resources, the destructor becomes a safeguard to clean up resources if the call to the Dispose method failed.

https://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

是的,您不应该从终结器访问其他托管的 类,或者当终结器调用该方法时从 Dispose 访问其他托管的 类。到执行对象的终结器时,它曾经引用的任何托管对象的状态都是不确定的。它们可能仍然存在,可能正在等待最终确定,或者可能已经被垃圾收集。此外,终结器 运行 在不同的线程上。

这是一个 .NET 1.x 实现细节,难以置信 难以摆脱。部分问题肯定是大多数关于 .NET 的书籍都是在 2005 年之前写的,它们都有一章是关于终结器的,而且它们都完全错误。

粗略地说,如果您认为需要终结器,那么 99.9% 的情况下您都错了。可终结的资源需要包装在它自己的 class 中,并且 that class 需要有一个终结器。如果您认为 需要实施一次性模式,那么大约 95% 的情况下您都错了。这里绝对是错误的。但有时如果你的基地 class 已经犯了实施它的错误,你别无选择。几个 .NET Framework classes 有这个错误,Microsoft 无法再修复这个问题。

上一章中class的与终结器不是你应该自己实现的。它已经写好了,.NET 2.0 获得了 SafeHandle classes。如果框架中未提供 Release() 方法,您充其量可能需要派生自己的 class。 SafeBuffer 对于基于指针的资源很有用。

最大的不同在于这些 classes 非常特别,它们有 critical finalizers。这是一个昂贵的词,意味着它们保证 运行,即使终结器线程因未处理的异常而崩溃。在大多数 LOB 应用程序中实际上并不那么重要,如果它们着火了,那么 OS 清理通常就足够了。但是,当 .NET 代码 运行 在具有较长正常运行时间保证的主机中时,这是必不可少的。 SQL 服务器就是一个很好的例子,不得不重新启动它,因为太多的 SQL/CLR 代码轰炸和泄露的句柄对业务不利。