代码之家  ›  专栏  ›  技术社区  ›  supercat

在失败的初始值设定项或构造函数中处理iDisposable

  •  6
  • supercat  · 技术社区  · 14 年前

    在.Net中有什么好的模式可以确保这一点吗 IDisposable 如果在构造期间(可能是在字段初始值设定期间)引发异常,对象拥有的字段将被释放?在Try/Catch块中包围字段初始值设定项的唯一方法是如果该块在对构造函数的调用之外,这将使清理代码很难正确地处理任何内容。

    我能想到的唯一方法是从基类继承对象,基类的构造函数采用类似于 ,并将该数组中的第一项设置为指向自身。子类中的所有构造函数都应该是私有的或受保护的,并且包含该参数。实例化应该通过工厂方法,它将声明一个 可识别的

    该方法可以通过让对象保留一个它创建的可iDisposable对象的列表来扩展,以便在不必显式地处理每个对象的情况下清理对象;这样的列表与工厂方法调用dispose方法结合使用会很有用,但在很大程度上与它正交。

    5 回复  |  直到 4 年前
        1
  •  12
  •   chilltemp    14 年前

    您应该捕获构造函数中的任何异常,然后处理子对象,然后重新引发原始异常(或提供附加信息的新异常)。

    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;
        }
      }
    }
    
        2
  •  1
  •   JMarsch    14 年前

    对象的构造函数将不负责创建它所拥有的IDisposable对象。相反,工厂将创建每个IDisposable,并调用所有者对象上的构造函数。然后,工厂将owner对象中的适当成员设置为创建的一次性对象。

    
    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??
    }
    
    
        3
  •  1
  •   supercat    14 年前

    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
    

    由于raitest继承了raibase(字符串的),要创建类实例,请使用字符串参数调用newraitest。RAII()是一个泛型函数,将其参数注册为需要清理的iDisposable,然后返回它。当对主对象调用Dispose或在主对象的构造中引发异常时,所有已注册的可处置项都将被处置。

    以下是riaaBase类:

    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 = allInnerExceptions
        End Sub
        Public Overridable ReadOnly Property DisposalExceptions() As Exception()
            Get
                Return _DisposalExceptions
            End Get
        End Property
    End Class
    
    Public Class raiiBase(Of T)
        Implements IDisposable
    
        Protected raiiList As Stack(Of IDisposable)
        Protected creationParam 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 constructionFailureException 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 As Exception
                    Dim exList As New List(Of Exception)
                    exList.Add(ex)
                    While theEnum.MoveNext
                        Try
                            theEnum.Current.Dispose()
                        Catch ex2 As Exception
                            exList.Add(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
    

    请注意,如果对任何或所有已注册的一次性对象的处置失败,将引发自定义异常类型。InnerException将指示构造函数是否失败;要查看哪些处理器失败,请检查DisposalExceptions。

        4
  •  0
  •   0xDEAD BEEF    14 年前

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

        5
  •  -2
  •   Steve Ellinger    14 年前

    在C#中,您将使用“using”:

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

    使用这些方法时,即使发生异常,也保证调用Dispose方法。您可以在Dispose方法中释放由初始值设定项(或构造函数)创建的任何资源。

    推荐文章