开发者

Is it safe to access a reference type member variable in a finalizer?

开发者 https://www.devze.com 2022-12-20 23:02 出处:网络
In other words, class Foo { object obj; Foo() { obj = new object(); } ~Foo() { obj.ToString(); /* Null开发者_运维百科ReferenceException? */ }

In other words,

class Foo
{
    object obj;
    Foo() { obj = new object(); }
    ~Foo() { obj.ToString(); /* Null开发者_运维百科ReferenceException? */ }
}


It's not safe since obj might have already been garbage collected. Also note that the garbage collector will not set the reference to null. So even checking for obj != null will not help you.

See here for details: http://msdn.microsoft.com/en-us/magazine/cc163392.aspx#S3

"Generalizing this principle, in a Dispose method it’s safe to clean up all resources that an object is holding onto, whether they are managed objects or native resources. However, in a finalizer it is only safe to clean up objects that are not finalizable, and generally the finalizer should only be releasing native resources." (Your obj is finalizable, so you shouldn't touch it in another finalizer)

That's also the reason why you have the

if (disposing) {...}

in the IDisposable pattern (see Figure 2 in the link above).


From Object.Finalize:

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 finalized when the finalizer of Object A starts.

In short, you can't make any assumptions about the state of referenced objects during a finalizer.

In virtually all circumstances, the logic implemented in a finalizer belongs in the Disposable pattern. This is an example of how to correctly implement the pattern in .NET using the IDisposable interface.

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;
    }
}

In the unlikely event you're working with unmanaged resources, have a look at this article for how to implement IDisposable with a finalizer:

MDSN: Implementing Finalize and Dispose to Clean Up Unmanaged Resources


When an object registers for finalization, it is placed on the finalization queue. When the garbage collector runs, all objects are divided into three categories:

  1. Those which have a rooted live reference other than the finalization queue.
  2. Those for which the only rooted reference is the finalization queue.
  3. Those for which no rooted reference exists.

Objects of the third type will simply cease to exist, though nothing will ever notice. Those of the first type are considered "live". Those of the middle type (including objects with finalizers, and other objects to which finalizable objects hold references), will have their finalizers (if any) run; once the finalizers are completed, unless the finalizers store rooted references someplace or re-register the objects for finalization, the objects will no longer have any rooted references at all and will be eligible for the next garbage collection.

The only real danger using references to other objects during finalization is that unless the objects have suitable interlocks, there's no way of knowing what state they might be in when the finalizer tries to clean them up; it's possible they might even be in use. It is probably a good idea to use Threading.Interlocked.Exchange to test-and-set a flag to indicate cleanup is in progress.

BTW, a couple of additional points Microsoft doesn't emphasize in their documentation:

  1. Because any objects to which finalizable objects hold direct or indirect references are ineligible for garbage collection, objects with finalizers shouldn't hold references to any objects not needed for finalization.
  2. Although Microsoft's Dispose pattern attempts to facilitate cleanup of objects with both managed and unmanaged resources (I think better terms would be self-cleaning and non-self-cleaning), such objects are almost always going to hold resources to objects not needed during finalization (falling afoul of rule #1).

Better, IMHO, would be to adopt the following principle: non-self-cleaning resources should be placed in their own classes, whose sole purpose is to handle their cleanup and expose the objects for use elsewhere; those should be the only classes with finalizers. Only classes derived from Object should implement finalizers; other derived classes which add non-self-cleaning resources should encapsulate those resources into separate self-cleaning classes, and then hold references to them.


In general, you don't want to implement finalizers on your objects. If you need to perform resource cleanup of managed objects, you want to do that in Dispose and properly implement the Dispose pattern.

If you do end up implementing a finalizer, you only want to access unmanaged resources.


If this is a concern, then it's probably better to dispose than finalize.

0

精彩评论

暂无评论...
验证码 换一张
取 消