我可以在终结器中安全地访问引用类型实例字段/属性吗?

时间:2013-11-23 19:56:37

标签: c# .net dispose finalizer

我一直认为答案是否定的,但我找不到任何来源说明这一点。 在下面的课程中,我是否可以在终结器中访问C实例的(托管)字段/属性,即ReleaseUnmanaged()?有什么限制,如果有的话? GC或终结是否会将这些成员设置为空?

我唯一能找到的是终结队列中的东西可以按任意顺序完成。因此,在这种情况下,由于recommendation类型应该允许用户多次调用Dispose(),为什么建议的模式会使用disposing boolean?如果我的终结器调用Dispose(true)而不是Dispose(false)

,可能会发生什么不好的事情
public class C : IDisposable
{
    private void ReleaseUnmanaged() { }

    private void ReleaseOtherDisposables() { }

    protected virtual void Dispose(bool disposing)
    {
        ReleaseUnmanaged();
        if (disposing)
        {   
            ReleaseOtherDisposables();
        }
    }

    ~ C()
    {
        Dispose(false);
    }

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

2 个答案:

答案 0 :(得分:4)

一般情况下 - 是的,你可以。如果一个类有非空的终结器,第一次GC会收集这个类的实例,它会调用终结器(只有你之前没有调用GC.SuppressFinalize)。从终结器看到的对象实例看起来就像上次触摸它时一样。您甚至可以创建从根到您的实例的新(直接或间接)链接,从而resurrect它。

即使你持有非托管对象的非托管指针并检查原始内存内容,你也不应该看到部分解除分配的对象,因为.NET使用复制GC。如果实例在收集期间处于活动状态,则会将其提升为下一代,或者与其他实例一起移动到全新的内存块。如果无法访问,则将其保留在原来的位置,或者释放整个堆并返回到OS。但请记住,终结器可以并且将在未能构造的对象的实例上调用(即,在对象构造期间抛出异常时)。

编辑:对于编写良好的课程中的Dispose(true) vs Dispose(false),从长远来看,应该没有多大区别。如果您的终结器名为Dispose(true),它只会删除您的对象到其他对象的链接,但由于您的对象已无法访问,因此释放对象引用的其他实例与其可访问性无关。

有关.NET GC实现详细信息的更多详细信息,我建议Joseph和Ben Albahari C# 5.0 in a Nutshell

答案 1 :(得分:3)

如果有任何代码可以通过任何方式获取对它的引用,GC将永远不会收集任何对象。如果对象中存在的唯一引用是弱引用,则GC将使它们无效,以便任何代码都无法获得对该对象的引用,于是它将能够收集它。

如果一个对象有一个活动的终结器,那么如果GC 收集它(但是对于终结器的存在),GC将把它添加到一个对象队列,其终结器应该是尽快运行,并且已经这样做,取消激活它。队列中的引用将阻止GC收集对象,直到终结器运行;一旦终结器完成,如果该对象没有其他引用并且它没有重新注册其终结器,它将不再存在。

终结器访问外部对象的最大问题是:

  • 终结器将在线程上下文中运行,该上下文与使用该对象的任何线程上下文无关,但不应执行任何无法保证快速完成的操作。这通常会产生矛盾的要求,即代码在访问其他对象时会使用锁定,但代码在等待锁定时不会阻塞。

  • 如果终结者有另一个也有终结者的对象的引用,则无法保证哪个对象会先运行。

这两个因素严重限制了具有终结器的对象的能力,以便能够访问他们不拥有的外部对象。此外,由于实现了最终化的方式,系统可以在没有强引用时决定运行终结器,但是在终结器运行之前创建和使用外部强引用;终结者的对象在是否发生这种情况时没有发言权。