Handling iDisposable in a failed initializer or constructor

Is there any good template in .Net to ensure that iDisposable fields belonging to an object are deleted if an exception is thrown during construction, possibly during a field initializer? The only way the spatial field initializers in the Try / Catch block is that the block is outside the constructor call, which makes it difficult to correctly clear the cleanup code.

The only approach I can imagine is to inherit an object from the base class, the constructor of which takes something like an iDisposable array and sets the first element in this array to point to itself. All descendant class constructors must be Private or Orotected and include this parameter. Instant action should be performed using factory methods that will declare an array of one iDisposable and pass it to the appropriate constructor. If the constructor fails, the factory method will have a reference to the partially constructed object, which it can then discard (the dispose method should, of course, be prepared to accept the possibility that the object cannot be completely constructed).

This approach can be expanded due to the fact that the object maintains a list of iDisposable objects to be created, in order to allow cleaning objects without the need for their explicit disposal; such a list would be useful in combination with the factory -method-calls-dispose approach, but largely orthogonal to it.

Any thoughts?

+6
constructor idisposable
source share
5 answers

I came up with a template that seems pretty good. This inspired someone posted on CodeProject.com - using a list to track supplies; raiiBase (of T) is a base class suitable for any class whose constructor takes one parameter. The constructor of the class must be protected, and the construction must be performed using the factory method. The static constructor makeRaii () takes a delegate to the factory function, which should accept Stack (iDisposable) along with the parameter of the expected type of the class. Usage example:

 Public Class RaiiTest
     Inherits raiiBase (Of String)
     Dim thing1 As testDisposable = RAII (New testDisposable ("Moe" & creationParam, "a"))
     Dim thing2 As testDisposable = RAII (New testDisposable ("Larry" & creationParam, "b"))
     Dim thing3 As testDisposable = RAII (New testDisposable ("Shemp" & creationParam, "c"))
     Dim thing4 As testDisposable = RAII (New testDisposable ("Curly" & creationParam, "d"))

     Protected Sub New (ByVal dispList As Stack (Of IDisposable), ByVal newName As String)
         MyBase.New (dispList, newName)
     End sub

     Private Shared Function _newRaiiTest (ByVal dispList As Stack (Of IDisposable), ByVal theName As String) As RaiiTest
         Return New RaiiTest (dispList, theName)
     End function

     Public Shared Function newRaiiTest (ByVal theName As String) As RaiiTest
         Return makeRaii (Of RaiiTest) (AddressOf _newRaiiTest, theName)
     End function

     Shared Sub test (ByVal st As String)
         Try
             Using it As RaiiTest = newRaiiTest (st)
                 Debug.Print ("Now using object")
             End using
             Debug.Print ("No exceptions thrown")
         Catch ex as raiiException
             Debug.Print ("Output exception:" & ex.Message)
             If ex.InnerException IsNot Nothing Then Debug.Print ("Inner exception:" & ex.InnerException.Message)
             For Each exx As Exception In ex.DisposalExceptions
                 Debug.Print ("Disposal exception:" & exx.Message)
             Next
         Catch ex as exception
             Debug.Print ("Misc. Exception:" & ex.Message)
         End try
     End sub
 End class

Since raiiTest inherits raiiBase (from String) to create an instance of the class, call newRaiiTest with a string parameter. RAII () is a generic function that will register its argument as iDisposable, which will need to be cleared and then returned. All registered disposable items will be selected if either Dispose is called on the main object, or when an exception is thrown in the structure of the main object.

Here's the riaaBase class:

 Option Strict On Class raiiException Inherits Exception ReadOnly _DisposalExceptions () As Exception Sub New (ByVal message As String, ByVal InnerException As Exception, ByVal allInnerExceptions As Exception ()) MyBase.New (message, InnerException) _DisposalExceptions = allInnerExceptable Read Subne DisposalExceptions () As Exception () Get Return _DisposalExceptions End Get End Property End Class Public Class raiiBase (Of T) Implements IDisposable Protected raii List As Stack (Of IDisposable) Protected creation Param As T Delegate Function raiiFactory (Of TT As raiiBase (Of T)) (ByVal theList As Stack (Of IDisposable), ByVal theParam As T) As TT Shared Function CopyFirstParamToSecondAndReturnFalse (Of TT) (ByVal P1 As TT, ByRef P2 As TT) As Boolean P2 = P1 Return False End Function Shared Function makeRaii (Of TT As raiiBase (Of T)) (ByVal theFactory As raiiFactory (Of TT), ByVal theParam As T) As TT Dim dispList As New Stack (Of IDisposable) Dim constructionFailureExcept  ion As Exception = Nothing Try Return theFactory (dispList, theParam) Catch ex As Exception When CopyFirstParamToSecondAndReturnFalse (ex, constructionFailureException) 'The above statement let us find out what exception occurred without having to catch and rethrow Throw' Should never happen, since we should have returned false above Finally If constructionFailureException IsNot Nothing Then zapList (dispList, constructionFailureException) End If End Try End Function Protected Sub New (ByVal DispList As Stack (Of IDisposable), ByVal Params As T) Me.raiiList = DispList Me.creationParam = Params End Sub Public Shared Sub zapList (ByVal dispList As IEnumerable (Of IDisposable), ByVal triggerEx As Exception) Using theEnum As IEnumerator (Of IDisposable) = dispList.GetEnumerator Try While theEnum.MoveNext theEnum.Current.Dispose () End While Catch ex Exception Dim exList As New List (Of Exception) exList.Add (ex) While theEnum.MoveNext Try theEnum.Current.Dispose () Catch ex2 As Exception exList.A  dd (ex2) End Try End While Throw New raiiException ("RAII failure", triggerEx, exList.ToArray) End Try End Using End Sub Function RAII (Of U As IDisposable) (ByVal Thing As U) As U raiiList.Push (Thing ) Return Thing End Function Shared Sub zap (ByVal Thing As IDisposable) If Thing IsNot Nothing Then Thing.Dispose () End Sub Private raiiBaseDisposeFlag As Integer = 0 'To detect redundant calls' IDisposable Protected Overridable Sub Dispose (ByVal disposing As Boolean) If disposing AndAlso Threading.Interlocked.Exchange (raiiBaseDisposeFlag, 1) = 0 Then zapList (raiiList, Nothing) End If End Sub #Region "IDisposable Support" 'This code added by Visual Basic to correctly implement the disposable pattern.  Public Sub Dispose () Implements IDisposable.Dispose 'Do not change this code.  Put cleanup code in Dispose (ByVal disposing As Boolean) above.  Dispose (True) GC.SuppressFinalize (Me) End Sub #End Region End Class 

Note that a custom exception type will be thrown if deletion fails for any or all registered disposable objects. An InnerException indicates whether the constructor failed; To see which utilities failed to execute, check ExposalExceptions.

+1
source share

You must catch any exceptions in the constructor, then get rid of your child objects, and then restore the original exception (or a new exception that provides additional information).

public class SomethingDisposable : IDisposable { System.Diagnostics.Process disposableProcess; public SomethingDisposable() { try { disposableProcess = new System.Diagnostics.Process(); // Will throw an exception because I didn't tell it what to start disposableProcess.Start(); } catch { this.Dispose(); throw; } } public void Dispose() { if (disposableProcess != null) { disposableProcess.Dispose(); disposableProcess = null; } } } 
+12
source share

Holding a partially constructed object sounds dangerous to me if it even works. I would not use initializers or ctor to handle this.

How about you instead use a factory object (not quite the same as the factory class) to create your object.

The constructor of your object is not responsible for creating the IDisposable objects that it owns. Instead, the factory will create each IDisposable, and it will call the constructor on your owner object. factory then set the appropriate members in the owner object to the created disposable objects.

pseudo code:

 public superobject CreateSuperObject() { IDisposable[] members = new IDisposable[n] try SuperObject o = new SuperObject() // init the iDisposable members, add each to the array, (you will probably also nee o.DisposableMember1 = new somethingdisposeable(); members[0] = o.DisposeableMember1 return o; catch // loop through the members array, disposing where not null // throw a new exception?? }
public superobject CreateSuperObject() { IDisposable[] members = new IDisposable[n] try SuperObject o = new SuperObject() // init the iDisposable members, add each to the array, (you will probably also nee o.DisposableMember1 = new somethingdisposeable(); members[0] = o.DisposeableMember1 return o; catch // loop through the members array, disposing where not null // throw a new exception?? } 
+1
source share

Oddly enough, but it looks like GC still calls the destructor for IDisposable objects, even if they throw an exception in the constructor! :)

 using (crazy = new MyDisposable()) <-- constructor throws { } <-- dispose wont get called ... somewhen in far future ~MyDisposable() <-- GC kicks in. 

If you were smart enough to use the example from msdn, where they called Dispose (false) from the destructor - well, you just failed! :)

0
source share

In C #, you should use 'using':

  using(DisposableObject obj = new DisposableObject()) { } 

VB also has a use ... End Using a construct. When using the Dispose method, it is guaranteed to be called even in the event of an exception. You can free any resources created by the initializers (or constructor) in the Dispose method.

-2
source share

All Articles