在终结器中访问引用类型成员变量是否安全?

时间:2010-02-24 15:53:56

标签: .net finalizer

换句话说,

class Foo
{
    object obj;
    Foo() { obj = new object(); }
    ~Foo() { obj.ToString(); /* NullReferenceException? */ }
}

5 个答案:

答案 0 :(得分:7)

来自Object.Finalize

  

两个对象的终结器不保证以任何特定顺序运行,即使一个对象引用另一个对象。也就是说,如果对象A具有对象B的引用并且两者都具有终结器,则当对象A的终结器开始时,对象B可能已经完成。

简而言之,您无法在终结器中对引用对象的状态做出任何假设。

在几乎所有情况下,终结器中实现的逻辑都属于Disposable模式。这是如何使用IDisposable接口在.NET中正确实现模式的示例。

public class MyClass : IDisposable
{
    private bool _disposed;

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

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if(disposing)
            {
                // Release unmanaged resources.
            }

            // Release managed resources (Streams, SqlConnections, etc.)
        }

        _disposed = true;
    }
}

如果您正在处理非托管资源,请查看本文,了解如何使用终结器实现IDisposable

MDSN: Implementing Finalize and Dispose to Clean Up Unmanaged Resources

答案 1 :(得分:7)

由于obj可能已被垃圾收集,因此不安全。另请注意,垃圾收集器不会将引用设置为null。因此,即使检查obj!= null也无济于事。

详情请见此处: http://msdn.microsoft.com/en-us/magazine/cc163392.aspx#S3

概括这个原则,在Dispose方法中,可以安全地清理对象所持有的所有资源,无论它们是托管对象还是本机资源。但是,在终结器中它只是安全的清理不可终结的对象,通常终结者应该只释放原生资源。 “(你的obj是可终结的,所以你不应该在另一个终结者中触摸它)

这也是你拥有

的原因

if(disposing){...}

IDisposable模式中的

(参见上面链接中的图2)。

答案 2 :(得分:3)

当一个对象注册完成时,它将被置于终结队列中。垃圾收集器运行时,所有对象分为三类:

  1. 具有根除终结队列的实时引用的那些。
  2. only rooted引用为终结队列的那些。
  3. 不存在有根引用的那些。

第三种类型的物体将永远不复存在,但没有人会注意到。第一类的那些被认为是“活的”。中间类型的那些(包括具有终结器的对象,和可终结对象持有引用的其他对象)将具有其终结器(如果有的话)运行;一旦终结器完成,除非终结器在某处存储有根引用或重新注册对象以进行最终化,否则对象将不再具有任何有根引用,并且有资格进行下一次垃圾回收。

在最终确定期间使用对其他对象的引用的唯一真正危险是,除非对象具有合适的互锁,否则当终结器试图清理它们时,无法知道它们可能处于什么状态;它们甚至可能在使用中。使用Threading.Interlocked.Exchange测试并设置一个标志以指示正在进行清理可能是一个好主意。

BTW,微软在他们的文档中没有强调的另外几点:

  1. 由于可终结对象持有直接或间接引用的任何对象都不符合垃圾回收的条件,因此具有终结器的对象不应包含对最终化不需要的任何对象的引用。
  2. 虽然Microsoft的Dispose模式试图利用托管和非托管资源来促进对象的清理(我认为更好的术语是自清洁和非自清理),但这些对象几乎总是会保留资源在最终确定期间不需要的对象(违反规则#1)。

更好的是,恕我直言,将采用以下原则:非自我清洁资源应放在他们自己的班级中,其唯一目的是处理他们的清理并将物品暴露在别处使用;那些应该是唯一有终结者的类。只有派生自Object的类才能实现终结器;其他添加非自清洁资源的派生类应该将这些资源封装到单独的自清理类中,然后保存对它们的引用。

答案 3 :(得分:1)

通常,您不希望在对象上实现终结器。如果需要对托管对象执行资源清理,则需要在Dispose中执行此操作并正确实现Dispose模式。

如果您最终实现终结器,则只需访问非托管资源。

答案 4 :(得分:0)

如果这是一个问题,那么处理可能比完成更好。