2009-12-16 31 views
8

Chỉnh sửa: Hai tùy chọn được hiển thị bên dưới.Cách tốt nhất để trả lại IDisposables được xây dựng một cách an toàn là gì?

Nếu bạn chỉ cần sử dụng chức năng mà IDisposable cung cấp, mệnh đề có tên thích hợp là using hoạt động tốt. Nếu bạn đang gói một IDisposable trong một đối tượng, đối tượng chứa chính nó cần phải là IDisposable và bạn cần triển khai mẫu phù hợp (một lớp IDisposable bị đóng kín hoặc lộn xộn hơn nhưng standard virtual pattern).

Nhưng đôi khi một phương pháp nhà máy trợ giúp là tốt cho sự sạch sẽ. Nếu bạn trả lại IDisposable trực tiếp sau khi xây dựng, bạn ổn, nhưng nếu trước tiên bạn xây dựng và sau đó sửa đổi hoặc thực thi mã có thể ném ngoại lệ trước khi quay trở lại, bạn cần gọi an toàn .Dispose() - nhưng chỉ nếu có một lỗi.

Ví dụ, mã không an toàn có thể trông như thế này ...

DbCommand CreateCommandUnsafely(string commandText) 
{ 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; //what if this throws? 
    return newCommand; 
}  

Solutions Hai biến thể an toàn sau ...

DbCommand CreateCommandSafelyA(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     success=true; 
     return newCommand; 
    } finally{ 
     if (!success && newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
    } 
} 


DbCommand CreateCommandSafelyB(string commandText) 
{ 
    DbCommand newCommand = null; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     return newCommand; 
    } catch { 
     if (newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
     throw; 
    } 
} 

Safe biến thể A chỉ là một dòng còn là , nhưng có vẻ là cách tiếp cận thành ngữ. Có vẻ như không có bất kỳ giải pháp thực sự ngắn gọn nào, mặc dù một số áp phích bên dưới cung cấp một số tùy chọn sử dụng lambda để trích xuất gói gọn này.

Mã sưng lên với bất kỳ phương pháp an toàn trên vẫn còn, và đặc biệt tiết tăng nặng với mã mà ban đầu trông giống như ...

return new MyDisposableThing { 
    OptionA = "X", 
    OptionB = B.Blabla, 
    Values = src.Values.Where(priority => priority > 1.0), 
}; 

Đoạn mã trên được viết một cách an toàn là khá lâu hơn một chút và ít có thể đọc được bởi vì bạn không còn có thể sử dụng cú pháp setter rút gọn nữa.

Trả lời

4

Tôi tin rằng đây là mô hình tiêu chuẩn:

DbCommand CreateCommand(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try 
    { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; 
     success = true; 
     return newCommand; 
    } 
    finally 
    { 
     if (!success & newCommand != null) 
      newCommand.Dispose(); 
    } 
} 

Nó không bắt và rethrow lỗi.

+0

Điều này trông giống như mã tôi thấy ở nơi khác. Bạn có biết tại sao điều này là thích hợp hơn? –

+1

Bắt ngoại lệ tùy ý (vì các câu trả lời khác dường như biện hộ) gây ra nhiều vấn đề trong trường hợp đặc biệt và nên tránh bất cứ khi nào có thể. Bằng cách đặt việc xử lý bên trong câu lệnh 'finally', mẫu chuẩn tránh các ngoại lệ bắt buộc, và (khi tạo đối tượng thất bại) nó bắt chước hành vi của câu lệnh' using' danh nghĩa. –

7

Không - tôi nghĩ không có cách nào tốt hơn.

Tuy nhiên, bạn có thể viết một lớp helper:

public static class DisposeHelper 
{ 
    public static TDisposable DisposeOnError<TDisposable>(TDisposable dispoable, Action<TDisposable> action) 
    where TDisposable : IDisposable 
    { 
    try 
    { 
     action(dispoable); 
    } 
    catch(Exception) 
    { 
     disposable.Dispose(); 
     throw; 
    } 

    return disposable; 
    } 
} 

Vì vậy, bạn có thể viết:

return DisposeHelper.DisposeOnError(connection.CreateCommand(), cmd => cmd.CommandText = commandText); 

Tôi không chắc chắn, tuy nhiên, nếu đó thực sự là một cách tốt hơn.

2

Bạn có thể xem xét viết một Extension Method:

public static class Disposable 
{ 
    public static void SafelyDo<T>(this T disp, Action<T> action) where T : IDisposable 
    { 
     try 
     { 
      action(disp); 
     } 
     catch 
     { 
      disp.Dispose(); 
      throw; 
     } 
    } 
} 

này sẽ cho phép bạn viết mã như thế này:

var disp = new MyDisposable(); 
disp.SafelyDo(d => 
    { 
     d.Foo = "Ploeh"; 
     d.Bar = 42; 
    }); 
return disp; 
0

Tôi nghĩ rằng bạn đang overcomplicating vấn đề này.

Nếu phương pháp của bạn trả về một đối tượng dùng một lần, thì bạn đang nói "Tôi từ bỏ quyền sở hữu đối tượng này, tốt hơn hoặc tệ hơn". Nếu một lỗi xảy ra trong khi bạn đang xây dựng nó, thì tại sao điều đó lại tạo nên sự khác biệt? Mã gọi điện thoại vẫn sẽ vứt bỏ nó ngay cả khi bạn ném một ngoại lệ.

Ví dụ:

DbCommand CreateCommand(string commandText) { 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; // what if this throws? 
    return newCommand; 
} 

void UseCommand() { 
    using(var cmd = CreateCommand("my query goes here")) { 
     // consume the command 
    } 
} 

Edit: Thật không may, nếu một ngoại lệ được ném ra bên CreateCommand, biến cmd không bao giờ được thiết lập và các đối tượng sẽ không được xử lý một cách chính xác.

+3

Tôi không tin đó là sự thật. Vì biến cmd sẽ không bao giờ được gán, khối sử dụng sẽ không gọi cmd.Dispose(). –

+0

Vấn đề là ném vào CommandText sẽ có nghĩa là newCommand không bao giờ được trả về câu lệnh sử dụng, vì vậy nó sẽ không được xử lý ở đó. – Kleinux

+0

Điểm tốt, các bạn. Trong trường hợp đó tôi bỏ phiếu cho một cái gì đó dọc theo các dòng của phương pháp chung chung của winSharp93. –

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