Safe to call managed resource from Finalizer? (if

2019-07-13 10:48发布

问题:

Is it not safe to call:

component.Dispose(); (if i check null)

from the Finalizer if i alter the code to this:

~MyResource()
{
    Dispose();
}
public void Dispose()
{
 // Check to see if Dispose has already been called.
        if(!this.disposed)
        {
            if(component != null) component.Dispose();   // !!! //

            CloseHandle(handle);
            handle = IntPtr.Zero;

            disposed = true;

        }
    GC.SuppressFinalize(this);
}

I know this works - but is it safe?

(from the example below?)

Code example: (before alter code)

http://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx

using System;
using System.ComponentModel;

// The following example demonstrates how to create
// a resource class that implements the IDisposable interface
// and the IDisposable.Dispose method.

public class DisposeExample
{
// A base class that implements IDisposable.
// By implementing IDisposable, you are announcing that
// instances of this type allocate scarce resources.
public class MyResource: IDisposable
{
    // Pointer to an external unmanaged resource.
    private IntPtr handle;
    // Other managed resource this class uses.
    private Component component = new Component();
    // Track whether Dispose has been called.
    private bool disposed = false;

    // The class constructor.
    public MyResource(IntPtr handle)
    {
        this.handle = handle;
    }

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
        Dispose(true);
        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the
    // runtime from inside the finalizer and you should not reference
    // other objects. Only unmanaged resources can be disposed.
    protected virtual void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if(!this.disposed)
        {
            // If disposing equals true, dispose all managed
            // and unmanaged resources.
            if(disposing)
            {
                // Dispose managed resources.
                component.Dispose();
            }

            // Call the appropriate methods to clean up
            // unmanaged resources here.
            // If disposing is false,
            // only the following code is executed.
            CloseHandle(handle);
            handle = IntPtr.Zero;

            // Note disposing has been done.
            disposed = true;

        }
    }

    // Use interop to call the method necessary
    // to clean up the unmanaged resource.
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    // Use C# destructor syntax for finalization code.
    // This destructor will run only if the Dispose method
    // does not get called.
    // It gives your base class the opportunity to finalize.
    // Do not provide destructors in types derived from this class.
    ~MyResource()
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        Dispose(false);
    }
  }
  public static void Main()
  {
    // Insert code here to create
    // and use the MyResource object.
  }
}

回答1:

It's hard to determine whether it's safe to do without knowing the implementation details of 'component'. Finalization of objects is unordered and does not respect containment hierarchy, so while the 'component' instance may not be null, it may already have been finalized by the time you call Dispose on it. Often Dispose methods aren't written with that type of safety in mind because they only expect to be called before the object is finalized.

Before you'd even want to consider doing clever things like touching other objects in finalizers, read Chris Brumme's blog posts on the subject; this one is a good starting point.

Generally, except in very specific circumstances, and you know that the Dispose method of the contained object behaves as expected even when called when it has been finalized, I'd assume the pattern you have asked about is not safe and would stick with the recommended pattern of passing a bool disposing parameter and only touching managed objects if it is true.



回答2:

In .net, any object which is accessible via any means is guaranteed to exist, fields of such object may be accessed, and property accesses or method calls on that object may be dispatched, just as they would under any other circumstances. The only thing that is not guaranteed is whether or not the system will have already run the Finalize method on a referenced object. It is entirely possible that as a consequence of the system having already run Finalize on an object, methods that would previously have done something useful will no longer be able to do so, but that is a function of the object's Finalize() routine and has nothing to do with the garbage collector per se.