Constructor exceptions in the distributed object

Consider the following 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 suppose we are using the disposable object correctly:

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

How about this piece of code?

In this case, we cannot call the Dispose method because our object never exists.

We know that in this case the finalizer will be called, but there we can only dispose of CriticalFinilizedObjects, and we cannot dispose of objects C1 or C2.

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 differ in some details, but I think you can understand the key principle: if the constructor throws an exception, we forcibly release the received resources and suppress the completion.

Are there any other ideas, suggestions or better solutions?

PS Herb Sutter on his blog ( http://herbsutter.wordpress.com/2008/07/25/constructor-exceptions-in-cc-and-java/ ) opened the question, but he did not offer a solution.

+7
c #
source share
5 answers

What you are proposing is the conclusion I came to, and when I thought about this problem .

In general, do as much work as necessary to get the object in a healthy state in the constructor, but clear any expensive managed resources that you allocate in the exception handler if you cannot complete it successfully.

It is idiomatic in .NET to use a constructor to get an object in a state in which it can be used. Some people will suggest using a simple constructor followed by the Initialize method, where any “real” work is done to get the object in the correct state, but I cannot imagine a single wireframe class that does this, so it’s not an intuitive template for .NET developers, and therefore this should not be done in .NET, regardless of whether this is a reasonable convention on other languages ​​and platforms.

I think this is a rather rare case - typically a class that wraps a one-time resource, perceives it as a constructor parameter, and does not create it itself.

+8
source share

The best solution would be to not make ANY logic in the constructors. Just create an object, and that’s good. If you really need to do something in the constructor, encapsulate it with the try catch finally command, where you free up unmanaged resources.

0
source share

Do you really dispose of your deconstructor?

Are you trying to mainly use RAII?

My program is completely

 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(); } } } 
0
source share

Sets the constructor arguments as the very first. Assert that any subsequent logic will not throw an exception. This means that any data used in the constructor logic is valid for this particular use. For example:

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

in the case where an empty string will throw c1 an exception.

If you cannot guarantee that exceptions will not be thrown by checking input. Rewrite the code so you can. Since it will be a very strong smell. If the exception code is not users, but the provider of the provider changes. This will not be the last problem you get with their code.

0
source share

Wrapping the constructor body in an “attempt” and calling Dispose on failure is probably about as good as you can. Instead of Try-Catch, I would suggest Try-finally with the ok flag, which is set at the end of try. If something throws during your constructor, this will give the debugger the opportunity to look at the state of the system during the throw.

In vb.net, you can do something a little better, because vb.net supports filtering exceptions, and since vb.net runs field initializers after the constructor of the base class, and thus allows members of the base class to be used in the field initializers of the derived class.

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

0
source share

All Articles