简体   繁体   中英

how to dispose C# base class when derived class constructor generates an error

If a C# derived IDisposable class constructor generates an error, how to dispose of the already fully constructed IDisposable base class?

Since all fields in the class hierarchy are initialized before any constructor executes, is it safe for the derived constructor to call base.Dispose()? It violates the rule of not calling virtual methods until object is fully constructed, but I can't think of another way to do it, and my searching hasn't turned up anything about this scenario.

My view is that constructors should be light, not relying on external resources/etc that might throw an exception. The constructor should do enough to verify Dispose() can be safely called. Consider using containment instead of inheritance, or having a factory method do the work that might throw.

All derived class constructors must call Dispose on the object being constructed if they exit via an exception. Further, it's very difficult to write leak-proof classes if field initializers construct IDisposable instances or can fail. Too bad, since requiring objects to be declared in one place, initialized in a second, and cleaned up in a third is not exactly a recipe for coherent code.

I would suggest that the best pattern is probably something like:

class foo : baseFoo , IDisposable
{
    foo () : baseFoo
    {
        bool ok = false;
        try
        {
            do_stuff();
            ok = true; // Only after last thing that can cause failure
        }
        finally
        {
            if (!ok)
              Dispose();
        }
    }
}

Note that C++/CLI automatically implements that pattern, and can also automatically handle cleanup of IDisposable fields. Too bad the language seems like a pain in other ways.

PS--With relatively few exceptions, mostly revolving around predictable-cost shared immutable objects (eg brushes, fonts, small bitmaps, etc.), code which relies upon Finalize to clean up objects is broken . If an IDisposable is created, it must be disposed unless the code which created it has particular knowledge about the consequences of deferring disposal to a finalizer.

For Managed resources, this should not be a problem, they will get Garbage Collected. For Unmanaged resources, make sure you have a Finalizer defined for your object, that will ensure the Unmanaged resources are cleaned up.

Also, throwing exceptions from a constructor is considered very bad manners, It's better to provide a Factory Method to do the construction and error handling or to equip your object with an Initialize method that will throw the actual exception. That way the construction always succeeds and you're not left with these type of issues.


Correct, Dispose isn't called by the Garbage Collector, but a Finalizer is, which in turn needs to call Dispose. It's a more expensive technique, except when used correctly. I didn't say so in my answer, did I ;).

You can call into the GC to force a collection run and you can wait for all pending finalizers. It's still better to not put the exception generating code in the construstor or by placing a try/catch around the code in the constructor to ensure Dispose is called on these files in case of an error. You can always rethrow the exception later.

This solution builds on the proposal by @supercat. Initialization of any member that might throw must be performed in the constructor's try/catch block. With that condition met an exception thrown from any constructor will correctly dispose a fully or partially constructed base or derived class.

In this test code uncomment each of the four exceptions in turn and the program will output which of the Disposable resources were not disposed properly due to the constructor throwing an exception. Then uncomment the two Dispose calls and observe that everything gets cleaned up as it should.

    class DisposableResource : IDisposable
    {
        public DisposableResource(string id) { Id = id; }
        ~DisposableResource() { Console.WriteLine(Id + " wasn't disposed.\n"); }
        public string Id { get; private set; }
        public void Dispose() { GC.SuppressFinalize(this); }
    }

    class Base : IDisposable
    {
        public Base()
        {
            try
            {
                throw new Exception();      // Exception 1.
                _baseCtorInit = new DisposableResource("_baseCtorInit");
//              throw new Exception();      // Exception 2.
            }
            catch(Exception)
            {
//              Dispose();                  // Uncomment to perform cleanup.
                throw;
            }
        }

        public virtual void Dispose()
        {
            if (_baseFieldInit != null)
            {
                _baseFieldInit.Dispose();
                _baseFieldInit = null;
            }

            if (_baseCtorInit != null)
            {
                _baseCtorInit.Dispose();
                _baseCtorInit = null;
            }
        }

        private DisposableResource _baseFieldInit = new DisposableResource("_baseFieldInit");
        private DisposableResource _baseCtorInit;
    }

    class Derived : Base
    {
        public Derived()
        {
            try
            {
//              throw new Exception();      // Exception 3.
                _derivedCtorInit = new DisposableResource("_derivedCtorInit");
//              throw new Exception();
            }
            catch (Exception)
            {
//              Dispose();                  // Uncomment to perform cleanup.
                throw;
            }
        }

        public override void Dispose()
        {
            if (_derivedFieldInit != null)
            {
                _derivedFieldInit.Dispose();
                _derivedFieldInit = null;
            }

            if (_derivedCtorInit != null)
            {
                _derivedCtorInit.Dispose();
                _derivedCtorInit = null;
            }

            base.Dispose();
        }

        private DisposableResource _derivedFieldInit = new DisposableResource("_derivedFieldInit");
        private DisposableResource _derivedCtorInit;
    }

    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Derived d = new Derived();
            }
            catch (Exception)
            {
                Console.WriteLine("Caught Exception.\n");
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            Console.WriteLine("\n\nPress any key to continue...\n");
            Console.ReadKey(false);
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM