10

Consider folowing classes:

class C1 : IDisposable {...}

class C2 : IDisposable {...}

sealed class C3 : IDisposable
{
   public C3()
   {
      c1 = new C1();
      throw new Exception(); //oops!
   }
   ~C3()
   {
      //What can we do???
   }
   public void Dispose()
   {
      if ( c1 != null ) c1.Dispose();
      if ( c2 != null ) c2.Dispose();
   }

   private C1 c1;
   private C2 c2;
   //assume that this class does not contains native resources
}

Now, assume we correctly use disposable object:

using (var c3 = new C3())
{
}

What about this code snippet?

In this case we can't call Dispose method, because our object never exists.

We know, that in this case finalizer would be called, but there we can dispose only CriticalFinilizedObjects and we can't dispose C1 or C2 objects.

My solution is pretty simple:

sealed class C3 : IDisposable
{
   public C3()
   {
      try {
      c1 = new C1();
      throw new Exception(); //oops!
      c2 = new C2();
      }
      catch(Exception)
      {
        DisposeImpl();
        throw;
      }
   }
   ~C3()
   {
      //Not deterministically dispose detected!
      //write to log! 
      //Invalid class usage. Or not??
   }
   public void Dispose()
   {
      DisposeImpl();
   }
   private void DisposeImpl()
   {
     if ( c1 != null ) c1.Dispose();
     if ( c2 != null ) c2.Dispose();
     GC.SuppressFinalize(this); //all resources are released
   }

   private C1 c1;
   private C2 c2;
}

This solution may vary in some details but I think you can understand key principle: if constructor throws an exception we forcedly release acquired resources and suppress finalization.

Have any one other idias, suggestions or better solutions?

P.S. Herb Sutter in his blogpost (http://herbsutter.wordpress.com/2008/07/25/constructor-exceptions-in-c-c-and-java/) opened the question but he does not proposed a solution.

bryanbcook
  • 16,210
  • 2
  • 40
  • 69
Sergey Teplyakov
  • 11,477
  • 34
  • 49

5 Answers5

10

What you're proposing is the conclusion I came to as well when thinking about this issue.

In summary, do as much work as needed to get the object in a usable state in the constructor, but clean up any expensive managed resources you allocate in an exception handler if you can't complete it successfully.

It is idiomatic in .NET to use the constructor to get the object in a state where it can be used. Some people will suggest the use of a simple constructor followed by an Initialize method where any 'real' work is done to get the object in the right state, but I cannot think of a single framework class that does this, so it's not an intuitive pattern for .NET developers to follow, and thus should not be done in .NET irrespective of whether it is a reasonable convention in other languages and platforms.

I think it's a fairly rare case anyway - typically a class that wraps a disposable resource will take it as a constructor parameter rather than creating it itself.

Martijn
  • 13,225
  • 3
  • 48
  • 58
Greg Beech
  • 133,383
  • 43
  • 204
  • 250
  • 4
    The double step initialization problem can be a source of problems, starting from the fact that at one point you have an object that is not actually usable, how can you protect against improper usage? (checks in every method/property?) The code would more fragile for maintenance. The only place I know where the double construction is idiomatic is in Symbian C++, but that was because of the compiler not handling exceptions in construction properly, so it is more of a system-wide hack than a solution. – David Rodríguez - dribeas Jan 19 '10 at 10:18
  • This whole pattern comes up again with async initialization. A factory solves both problems nicely--hide the constructor so it can't be called, then have the factory construct and then initialize the object. This way the partially-constructed object can't be misused (except through reflection, of course). – James Aug 02 '16 at 01:12
0

Isnt your dispose in actuality your deconstructor?

Are you trying to basically use RAII?

My program in its entirety

using System;

namespace Testing
{
    class C1 : IDisposable
    {
        public C1()
        {
        }
        public void Dispose()
        {
            Console.WriteLine( "C1 Destroyed" );
        }
    }
    class C2 : IDisposable
    {
        public C2()
        {
            throw new Exception();
        }
        public void Dispose()
        {
            Console.WriteLine( "C2 Destroyed" );
        }
    }
    class C3 : IDisposable
    {
        C1 c1;
        C2 c2;
        public C3()
        {
            try {
                c1 = new C1();
                c2 = new C2();
            } catch {
                this.Dispose();
                throw new Exception();
            }
        }
        ~C3()
        {
            this.Dispose();

        }
        public void Dispose()
        {
            // basically an early deconstructor
            Console.WriteLine( "C3 Being Destroyed" );
            if ( c1 != null )
                c1.Dispose();
            if ( c2 != null )
                c2.Dispose();
            GC.SuppressFinalize(this);
            Console.WriteLine( "C3 Destroyed" );
        }
    }
    class MainClass
    {
        public static void Main(string[] args)
        {
            try {
                using ( var c3 = new C3() )
                {
                    Console.WriteLine("Rawr");
                }
            } catch {
                Console.WriteLine( "C3 Failed" );
            }
            GC.Collect();
        }
    }
}
David Stocking
  • 1,200
  • 15
  • 26
  • In this case you can't call Dispose method (we can't get object reference, because object is never exists). And I can't access c1 or c2 from Finalize method because ofinalizations methods order isn't determined (I can access only to CriticalFinilizerObjects, and this is not my case). – Sergey Teplyakov Jan 19 '10 at 08:57
  • Um I can actually compile and run this and it works just fine. The deconstructor still gets called for me at least. – David Stocking Jan 19 '10 at 09:29
  • 1
    First: that there is no destructor, only finalizer (and we can't call this method manually). Second: The CLR doesn't make any guarantees as to the order in which Finalize methods are called, so you should avoid writing a Finalize method that accesses other objects whose type defines a Finalize method; those other objects could have been finalized already. Jeffrey Richter – Sergey Teplyakov Jan 19 '10 at 09:59
0

Do asserts on your constructor arguments as the very first. Assert that any subsequent logic will not throw an exception. That is assert that any data used in the constructor logic is valid for that specific usage. Like so:

c3(string someString)
{
  Debug.Assert(!string.IsNullOrEmpty())
  c1 = new c1(someString);
}

in the case where an empty string would cause c1 throw an exception.

If you are not able to ensure that no exceptions will be throw by input validation. Rewrite the code so you can. Since that would be a very strong smell. If the exception throwing code is not users but a vendor change vendor. It will not be the last problem you get with their code then.

Rune FS
  • 21,497
  • 7
  • 62
  • 96
  • Debug.Assert(something) isn't particularly good option as it's available in debug code only. Usually recommended pattern is something like this: Debug.Assert(val != null); if ( val == null ) throw new exception(); – Bolek Tekielski Jan 19 '10 at 10:15
  • Let assume, that I can't control whether C1 or C2 constructors fails. – Sergey Teplyakov Jan 19 '10 at 10:49
  • then switch vendor, seriously. If you as an outsider can't reason sbout a method call (in this case a constructor call) then the code behind that method is purely written, practically in testable, since not being able to reason about the code means you wouldn't know what test data to use. Then your real problem is not cleaning up, that is fixing a symptom not a cause. Always fix the cause – Rune FS Jan 19 '10 at 10:54
  • Let assume, that C1 (or/and C2) is a wrapper for some data base table (or simply read/write data to DB), or it depends on something like configuration file that can be readonly (or even not exits) or it trying to open socket (but port already in use), or trying to connect to another server (but server doesn't listening), or uses specific format of an argument (and I can't validate it because I don't know specific format), or something else that I can't control. There are some circumstances that only class itself could know about and this something could be unreachable or in invalid state. – Sergey Teplyakov Jan 19 '10 at 11:22
  • Well I/O of any kind does not belong in a constructor. Constructors shall be fast and have very predictable behaviour. Accessing a DataBase is not something that belongs in a constructor. In the constructor you could set up how to connect to the DB, but you deffinately should _not_ connect to the DB or read any other external data sources. The only situation I can think of that might be sligthly different is reading a serialized object graph from DB/disc but then you shouldn't have any unmanaged resources in the object graph – Rune FS Jan 19 '10 at 12:01
  • Ok. What about exceptions during open server socket, errors during working with serial ports? (this is real examples from my application). All of this is invariants for some business classes. My class isn't properly initialized if TCP port already in use, or COM port does not exists. – Sergey Teplyakov Jan 19 '10 at 12:41
  • Connect5ing to a TCP port is an operation not an invariant same goes for COM ports. However if you insist on performing these operations in the constructor do them before you acquire any unmanaged resources. However think about this. Being connected is _not_ an invariant of the .NET TcpClient but an operation on. eventhough you can't use it for communication with out it being connected the object can be correctly _constructed_ which is what the constructor does – Rune FS Jan 21 '10 at 08:17
0

Wrapping the body of the constructor in a "try" and calling Dispose on failure is probably about as good a thing as you can do. Instead of "Try-Catch", though, I'd suggest "Try-Finally", with an "ok" flag which gets set at the end of the "try". If something throws during your constructor, that will give the debugger a chance to look at the system state at the time of the throw.

In vb.net, one can do things a little better, since vb.net supports exception filtering, and since vb.net runs field initializers after the base-class constructor, and thus allows base-class members to be used within the derived-class field initializers.

See my question Handling iDisposable in failed initializer or constructor along with my answer to it for more info.

Community
  • 1
  • 1
supercat
  • 77,689
  • 9
  • 166
  • 211
-1

A better solution would be not to do ANY logic in constructors. Just create the object and it's good to go. If you really need to do something in the constructor, encapsulate it with a try catch finally statement where you free up unmanaged resources.

Scoregraphic
  • 7,110
  • 4
  • 42
  • 64
  • I mean, that C1 is fully constructed, but C2 constractor throws an exception: c1 = new C1(); c2 = new C2(); //C2 ctor throws an exception – Sergey Teplyakov Jan 19 '10 at 08:36
  • if C2 is not your self-written class (but library), you should set `c2 = null` in the catch block, as you can't dispose it. – Scoregraphic Jan 19 '10 at 09:07
  • Or.. and public setters, right? You break so many principles at single time then don't know from which one I have to start. Single responsibility, possible side effects, DDD modeling, shared logic of object creation and validation, inconsistent edited objects... You must create entity and validate it in constructor and only in it. Don't suggest old school anti-patterns, please. and for unmanaged resource you need DI, not a try catch. You should not create something more complex then a string in constructor. Only injection and validation – Artem A Feb 09 '23 at 11:23
  • You know that this post is 13 years old, do you? – Scoregraphic Feb 10 '23 at 11:35