C# 我能否在终结器中安全地访问引用类型实例字段/属性?

C# 我能否在终结器中安全地访问引用类型实例字段/属性?,c#,.net,dispose,finalizer,C#,.net,Dispose,Finalizer,我一直认为答案是否定的,但我找不到任何来源说明这一点。 在下面的类中,我是否可以访问终结器中C实例的(托管)字段/属性,即releasenmanaged()?有哪些限制条件(如有)?GC或finalization会将这些成员设置为null吗 我唯一能找到的是,终结器队列中的内容可以按任何顺序终结。因此,在这种情况下,既然类型应该允许用户多次调用Dispose(),那么为什么推荐的模式需要使用disposing布尔值?如果我的终结器调用了Dispose(true)而不是Dispose(false)

我一直认为答案是否定的,但我找不到任何来源说明这一点。 在下面的类中,我是否可以访问终结器中
C
实例的(托管)字段/属性,即
releasenmanaged()
?有哪些限制条件(如有)?GC或finalization会将这些成员设置为null吗

我唯一能找到的是,终结器队列中的内容可以按任何顺序终结。因此,在这种情况下,既然类型应该允许用户多次调用
Dispose()
,那么为什么推荐的模式需要使用
disposing
布尔值
?如果我的终结器调用了
Dispose(true)
而不是
Dispose(false)
,会发生什么不好的事情


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

即使您持有指向未固定对象的非托管指针并检查原始内存内容,您也不应该能够看到部分解除分配的对象,因为.NET使用复制GC。如果一个实例在收集过程中处于活动状态,则它要么升级到下一代,要么与其他实例一起移动到全新的内存块。如果无法访问,则要么将其留在原来的位置,要么释放整个堆并返回到操作系统。但是,请记住,终结器可以并且将在构造失败的对象实例上被调用(即,在对象构造过程中何时抛出异常)

编辑:至于
Dispose(true)
vs
Dispose(false)
在编写良好的类中,从长远来看应该没有太大的区别。如果您的终结器调用了
Dispose(true)
,它只会删除从您的对象到其他对象的链接,但是由于您的对象已经不可访问,因此释放对象引用的其他实例不会影响它们的可访问性


有关.NET GC实现细节的更多详细信息,我推荐Joseph和Ben Albahari。

如果任何代码能够通过任何方式获得对任何对象的引用,GC将永远不会收集任何对象。如果存在于对象的唯一引用是弱引用,那么GC将使它们无效,这样任何代码都无法获得对该对象的引用,因此它将能够收集该引用

如果一个对象有一个活动的终结器,那么如果GC将收集它(但对于终结器的存在),GC将把它添加到一个对象队列中,该队列中的终结器应该尽快运行,并在完成后取消激活它。队列中的引用将阻止GC在终结器运行之前收集对象;一旦终结器完成,如果不存在对该对象的其他引用,并且该对象尚未重新注册其终结器,则该终结器将停止存在

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

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

  • 如果终结器引用了另一个也有终结器的对象,则无法保证哪个对象将首先运行


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

如前所述,避免为不拥有非托管资源的类编写终结器(即使这样,也尝试使用SafeHandle)。如果我理解正确,主要问题不是我无法访问字段/属性引用的对象,但是我没有确保线程安全的常规方法?在一个简化的例子中,我知道没有线程会与我的终结器同时访问“拥有的”对象,这就没有问题了?@Rob:我会说上面提到的两个问题是主要问题,但我不会将它们分别描述为主要问题。他们两人合谋使生活困难。例如,假设一个类封装了一个文件,另一个类封装了一个日志流(反过来又封装了该文件)。日志流有一个选项,可以设置检查点,声明某些记录为“暂定”,并删除自上一个检查点以来的所有暂定记录(其想法是“暂定”记录仅在……出现故障时才感兴趣的信息,然后在操作成功时放弃此类信息)。为了提高效率,这样的日志类通常必须在内部缓冲信息,除非或直到它知道实际上需要编写信息。如果放弃了记录器,它应该在关闭它封装的文件之前写出它所拥有的所有信息,但是为了实现这一点,可终结对象必须在某种静态位置存储对该文件的引用,以防止在记录器之前将其终结。
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);
    }
}