2010-09-29 37 views
6

Có bất kỳ mẫu đẹp nào trong .Net để đảm bảo rằng các trường iDisposable thuộc sở hữu của một đối tượng sẽ bị xử lý nếu một ngoại lệ được ném trong khi xây dựng, có thể trong trình khởi tạo trường? Cách duy nhất để bao quanh bộ khởi tạo trường trong khối Thử/Bắt là nếu khối nằm ngoài cuộc gọi đến hàm tạo, điều này sẽ khiến cho mã dọn dẹp trở nên khá khó khăn để xử lý đúng cách.Xử lý iDisposable trong initializer thất bại hoặc constructor

Cách tiếp cận duy nhất tôi có thể tìm là đối tượng kế thừa từ lớp cơ sở có hàm tạo giống như một mảng iDisposable và đặt mục đầu tiên trong mảng đó để trỏ tới chính nó. Tất cả các hàm tạo nên các lớp con cháu phải là Riêng tư hoặc Được bảo vệ và bao gồm tham số đó. Việc khởi tạo nên thông qua các phương thức factory, nó sẽ khai báo một mảng của một iDisposable và chuyển nó tới hàm tạo thích hợp. Nếu hàm tạo không thành công, phương thức factory sẽ có tham chiếu đến đối tượng được xây dựng một phần, sau đó nó có thể xử lý (phương thức vứt bỏ phải được chuẩn bị để chấp nhận khả năng đối tượng có thể không được xây dựng đầy đủ).

Cách tiếp cận có thể được mở rộng bằng cách để đối tượng giữ một danh sách các đối tượng iDisposable mà nó tạo ra, để cho phép các đối tượng được dọn sạch mà không phải vứt bỏ từng đối tượng; một danh sách như vậy sẽ hữu ích khi kết hợp với cách tiếp cận nhà máy-gọi-vứt bỏ, nhưng phần lớn là trực giao với nó.

Mọi suy nghĩ?

Trả lời

1

Tôi đã đưa ra một mẫu có vẻ khá tốt. Nó lấy cảm hứng từ một người nào đó được đăng trên CodeProject.com - sử dụng danh sách để theo dõi các lần gỡ bỏ; raiiBase (của T) là một lớp cơ sở phù hợp với bất kỳ lớp nào mà hàm tạo của nó có một tham số đơn. Trình xây dựng lớp phải được bảo vệ và việc xây dựng phải được thực hiện thông qua phương thức factory. Hàm tạo makeRaii() tĩnh nhận một đại biểu đến một hàm nhà máy, phải chấp nhận một Stack (của iDisposable) cùng với một tham số của kiểu dự kiến ​​của lớp.Sử dụng mẫu:

 
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 

Vì raiiTest kế thừa raiiBase (của chuỗi), để tạo một thể hiện lớp, hãy gọi newRaiiTest với tham số chuỗi. RAII() là một hàm chung sẽ đăng ký đối số của nó như là một iDisposable mà sẽ cần dọn dẹp, và sau đó trả về nó. Tất cả các lần gỡ bỏ đã đăng ký sẽ được xử lý khi Dispose được gọi trên đối tượng chính hoặc khi một ngoại lệ được ném vào việc xây dựng đối tượng chính.

Đây là lớp 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 

Lưu ý rằng một loại ngoại lệ tùy chỉnh sẽ được ném nếu xử lý lỗi vì bất kỳ hoặc tất cả các đối tượng dùng một lần đăng ký. InnerException sẽ cho biết liệu hàm tạo có bị lỗi hay không; để xem (các) trình xử lý nào không thành công, hãy kiểm tra DisposalExceptions.

1

Giữ đối tượng được xây dựng một phần có vẻ nguy hiểm đối với tôi, nếu nó thậm chí sẽ hoạt động. Tôi sẽ không sử dụng initializers hoặc một ctor để xử lý này.

Làm thế nào nếu thay vào đó, bạn sử dụng một nhà máy đối tượng (không hoàn toàn giống như một nhà máy lớp) để tạo đối tượng của bạn.

Trình tạo đối tượng của bạn sẽ không chịu trách nhiệm tạo các đối tượng IDisposable mà nó sở hữu. Thay vào đó, nhà máy sẽ tạo ra mỗi IDisposable và nó sẽ gọi hàm tạo trên đối tượng chủ sở hữu của bạn. Nhà máy sau đó sẽ thiết lập các thành viên thích hợp trong đối tượng chủ sở hữu cho các đối tượng dùng một lần đã được tạo ra.

giả:

 

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?? 
} 
 
-2

Trong C# bạn sẽ sử dụng 'sử dụng':

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

VB cũng có dùng ... End Using xây dựng. Khi sử dụng các phương pháp Vứt bỏ này được đảm bảo sẽ được gọi, ngay cả trong trường hợp ngoại lệ. Bạn có thể giải phóng bất kỳ tài nguyên nào được tạo bởi initializers (hoặc constructor) trong phương thức Dispose.

+3

-1 Điều này không có tác dụng nếu hàm tạo đưa ra một ngoại lệ. Đối tượng mới không bao giờ được trả về, vì vậy không có gì để gọi Dispose on. – chilltemp

+0

Hmmmm ... bị vỡ. –

12

Bạn nên bắt bất kỳ ngoại lệ nào trong hàm tạo, sau đó vứt bỏ đối tượng con của bạn, sau đó trả lại ngoại lệ ban đầu (hoặc ngoại lệ mới cung cấp thông tin bổ sung).

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; 
    } 
    } 
} 
+0

Có cách nào để bắt ngoại lệ trong trình khởi tạo hay không (ví dụ: "Phông chữ myFont = Phông chữ mới (" Arial ", ... bất kỳ ...);")? Trong nhiều trường hợp, việc tạo đối tượng khi chúng được xác định là thuận tiện hơn để xác định chúng ở một nơi và sau đó tạo chúng ở một nơi khác. – supercat

+1

@supercat: Không phải là tôi biết. Tôi đồng ý với sự đơn giản mà bạn tìm kiếm, nhưng trong trường hợp này là mạnh mẽ là quan trọng hơn. – chilltemp

+0

+1 nhưng tôi thích * không * để gọi 'Vứt bỏ' trong hàm tạo, bởi vì nếu bạn thực hiện cái gọi là mẫu' IDisposable' chuẩn thì bạn có thể kết thúc bằng một cuộc gọi ảo trong suốt cuộc gọi hàm tạo (thông qua 'void void Dispose (bool disposing) '). –

0

Như lạ như nó có vẻ, nhưng có vẻ như GC vẫn gọi destructor cho đối tượng IDisposable, ngay cả khi họ ném ngoại lệ trong constructor! :)

using (crazy = new MyDisposable()) <-- constructor throws 
{ 
} <-- dispose wont get called 

... somewhen in far future 
~MyDisposable() <-- GC kicks in. 

Nếu bạn đủ thông minh để sử dụng ví dụ từ msdn, nơi chúng được gọi là Vứt bỏ (sai) khỏi hàm hủy - tốt - bạn vừa thất bại! :)

+2

Một số đối tượng IDisposable sẽ hành xử chấp nhận ngay cả khi chúng không được xử lý, bởi vì một finalizer sẽ bắn trong thời trang chấp nhận kịp thời để làm sạch chúng. Vứt bỏ một vật thể có thể gây ra rò rỉ bộ nhớ lớn không thời hạn (ví dụ như người đăng ký sự kiện có tham chiếu đến nhiều đối tượng khác và đăng ký một sự kiện từ một đối tượng tồn tại lâu dài sử dụng Dispose to hủy bỏ sự kiện đó. Nếu vứt bỏ không cháy, không phải đối tượng đó, cũng như bất kỳ đối tượng nào mà nó chứa một tham chiếu, có thể được thu thập cho đến khi đối tượng tồn tại lâu dài chết.) – supercat

+0

Trong vb.net, tôi đã đưa ra một mẫu đối với các đối tượng dùng một lần cho phép khai báo đối tượng, khởi tạo và dọn dẹp, được xử lý trên một dòng đơn (xem bên dưới). Nó đòi hỏi các trình khởi tạo trường được chạy sau hàm tạo cơ sở, và do đó, có thể không thích ứng với C#. – supercat

Các vấn đề liên quan