在失败的初始化程序或构造函数中处理iDisposable

sup*_*cat 6 .net constructor idisposable

.Net中是否有任何好的模式用于确保在构造期间抛出异常时可能会丢弃对象拥有的iDisposable字段,可能是在字段初始化期间?在Try/Catch块中包围字段初始值设定项的唯一方法是,如果块在构造函数的调用之外,这将使清理代码很难正确处理任何内容.

我能想到的唯一方法是从对象继承对象,该对象的构造函数采用类似于iDisposable的数组,并将该数组中的第一项设置为指向自身.所有构造函数的后代类应该是Private或Orotected,并包含该参数.实例化应该通过工厂方法,它将声明一个iDisposable的数组并将其传递给适当的构造函数.如果构造函数失败,那么工厂方法将引用部分构造的对象,然后它可以处理(当然,dispose方法必须准备好接受对象可能没有完全构造的可能性).

可以通过让对象保留它创建的iDisposable对象列表来扩展该方法,以允许清理对象而无需明确地处理每个对象; 这样的列表可以与factory-method-calls-dispose方法结合使用,但在很大程度上与它正交.

有什么想法吗?

chi*_*emp 12

您应该在构造函数中捕获任何异常,然后处置您的子对象,然后重新抛出原始异常(或提供其他信息的新异常).

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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


sup*_*cat 1

我想出了一个看起来不错的模式。它的灵感来自于 CodeProject.com 上某人发布的文章——使用列表来跟踪一次性物品;raiiBase(of T) 是一个基类,适用于其构造函数采用单个参数的任何类。类构造函数必须受到保护,并且构造必须通过工厂方法完成。静态 makeRaii() 构造函数接受工厂函数的委托,该函数必须接受 Stack(of iDisposable) 以及类预期类型的​​参数。示例用法:

公开课 RaiiTest
    继承 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)
    结束子

    私有共享函数 _newRaiiTest(ByVal dispList As Stack(Of IDisposable), ByVal theName As String) As RaiiTest
        返回新 RaiiTest(dispList, theName)
    结束功能

    公共共享函数 newRaiiTest(ByVal theName As String) As RaiiTest
        返回 makeRaii(Of RaiiTest)(AddressOf _newRaiiTest, theName)
    结束功能

    共享子测试(ByVal st As String)
        尝试
            将其用作 RaiiTest = newRaiiTest(st)
                Debug.Print("现在使用对象")
            结束使用
            Debug.Print("没有抛出异常")
        捕获 ex 作为 raiiException
            Debug.Print("输出异常:" & ex.Message)
            如果 ex.InnerException IsNot Nothing 那么 Debug.Print("内部异常:" & ex.InnerException.Message)
            对于 ex.DisposalExceptions 中的每个 exx 作为异常
                Debug.Print("处理异常:" & exx.Message)
            下一个
        捕获 ex 作为异常
            Debug.Print("杂项异常:" & ex.Message)
        结束尝试
    结束子
结束课程

由于 raiiTest 继承了 raiiBase(of String),因此要创建类实例,请使用字符串参数调用 newRaiiTest。RAII() 是一个通用函数,它将其参数注册为需要清理的 iDisposable,然后返回它。当在主对象上调用 Dispose 或在构造主对象时抛出异常时,所有已注册的可处置对象都将被 Dispose。

这是 riaaBase 类:

选项严格开启
类 raiiException
    继承异常
    ReadOnly _DisposalExceptions() 作为异常
    Sub New(ByVal message As String, ByVal InnerException As Exception, ByVal allInnerExceptions As Exception())
        MyBase.New(消息,InnerException)
        _DisposalExceptions = allInnerExceptions
    结束子
    公共可重写只读属性 DisposalExceptions() As Exception()
        得到
            返回_DisposalExceptions
        结束获取
    结束财产
结束课程

公共类 raiiBase(Of T)
    实现 IDisposable

    受保护的 raiiList 作为堆栈(IDisposable)
    受保护的创建Param As T
    委托函数 raiiFactory(Of TT As raiiBase(Of T))(ByVal theList As Stack(Of IDisposable), ByVal theParam As T) As TT

    共享函数 CopyFirstParamToSecondAndReturnFalse(Of TT)(ByVal P1 As TT, ByRef P2 As TT) As Boolean
        P2 = P1
        返回错误
    结束功能

    共享函数 makeRaii(TT As raiiBase(Of T))(ByVal theFactory As raiiFactory(Of TT), ByVal theParam As T) As TT
        Dim dispList 作为新堆栈(IDisposable)
        Dim ConstructionFailureException As Exception = Nothing
        尝试
            返回 theFactory(dispList, theParam)
        当 CopyFirstParamToSecondAndReturnFalse(例如,constructionFailureException) 时捕获 ex 作为异常
            ' 上面的语句让我们可以找出发生了什么异常,而不必捕获并重新抛出
            Throw ' 永远不会发生,因为我们应该在上面返回 false
        最后
            如果 ConstructionFailureException IsNot Nothing 那么
                zapList(dispList,constructionFailureException)
            万一
        结束尝试
    结束功能

    受保护的子新(ByVal DispList 作为堆栈(IDisposable),ByVal Params 作为 T)
        Me.raiiList = DispList
        Me.creationParam = 参数
    结束子

    Public Shared Sub zapList(ByVal dispList As IEnumerable(Of IDisposable), ByVal triggerEx As Exception)
        使用 Enum 作为 IEnumerator(Of IDisposable) = dispList.GetEnumerator
            尝试
                而Enum.MoveNext
                    theEnum.Current.Dispose()
                结束时
            捕获 ex 作为异常
                Dim exList 作为新列表(例外)
                exList.Add(ex)
                而Enum.MoveNext
                    尝试
                        theEnum.Current.Dispose()
                    捕获 ex2 作为异常
                        exList.Add(ex2)
                    结束尝试
                结束时
                抛出新的raiiException(“RAII失败”,triggerEx,exList.ToArray)
            结束尝试
        结束使用
    结束子

    函数 RAII(Of U As IDisposable)(ByVal Thing As U) As U
        raiiList.Push(事物)
        归还东西
    结束功能

    共享 Sub zap(ByVal Thing As IDisposable)
        如果 Thing Not Nothing 那么 Thing.Dispose()
    结束子

    Private raiiBaseDisposeFlag As Integer = 0 ' 检测冗余调用

    ' I一次性
    受保护的可覆盖子处置(ByVal 处置为布尔值)
        如果处置 AndAlso Threading.Interlocked.Exchange(raiiBaseDisposeFlag, 1) = 0 那么
            zapList(raiiList, 无)
        万一
    结束子

#Region“IDisposable 支持”
    ' 此代码由 Visual Basic 添加以正确实现一次性模式。
    Public Sub Dispose() 实现 IDisposable.Dispose
        ' 不要更改此代码。将清理代码放入上面的 Dispose(ByVal disusing As Boolean) 中。
        处置(真)
        GC.SuppressFinalize(我)
    结束子
#结束区域

结束课程

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