2010-11-19 11 views
6

Tôi đã hỏi một câu hỏi liên quan đến returning a Disposable (IDisposable) object from a function, nhưng tôi nghĩ rằng tôi sẽ thảo luận nếu tôi nêu câu hỏi này ở đó.Tôi hoàn toàn không hiểu hoạt động của việc sử dụng/Các đối tượng dùng một lần

Tôi tạo ra một số mẫu mã:

class UsingTest 
{ 
    public class Disposable : IDisposable 
    { 
     public void Dispose() 
     { 
      var i = 0; 
      i++; 
     } 
    } 
    public static Disposable GetDisposable(bool error) 
    { 
     var obj = new Disposable(); 
     if (error) 
      throw new Exception("Error!"); 
     return obj; 
    } 
} 

tôi mã hoá nó theo cách này cố ý, bởi vì sau đó tôi gọi:

using (var tmp = UsingTest.GetDisposable(true)) { } 

Sử dụng trình gỡ lỗi, tôi nhận thấy rằng phương pháp Dispose không bao giờ thực hiện, thậm chí mặc dù chúng tôi đã khởi tạo một đối tượng Disposable. Nếu tôi hiểu chính xác mục đích của Dispose, nếu lớp học này thực sự đã mở tay cầm và tương tự, thì chúng tôi sẽ không đóng chúng ngay sau khi chúng tôi kết thúc với chúng.

Tôi đặt câu hỏi này vì hành vi này phù hợp với những gì tôi mong đợi, nhưng trong câu trả lời cho câu hỏi liên quan, mọi người dường như chỉ ra rằng using sẽ xử lý mọi thứ.

Nếu using vẫn bằng cách nào đó sẽ xử lý tất cả những điều này, ai đó có thể giải thích những gì tôi đang thiếu? Nhưng, nếu mã này thực sự có thể gây ra rò rỉ tài nguyên, làm thế nào bạn sẽ đề nghị tôi mã GetDisposable (với điều kiện mà tôi phải nhanh chóng đối tượng IDisposable và chạy mã mà có thể ném một ngoại lệ trước khi tuyên bố trở lại)?

Trả lời

4

Tùy thuộc vào những gì ngữ nghĩa mà bạn muốn cho GetDisposable, đây có lẽ là thế nào tôi sẽ thực hiện nó:

public static Disposable GetDisposable(bool error) 
{ 
    var obj = new Disposable(); 

    try 
    { 
     if (error) 
      throw new Exception("Error!"); 

     return obj; 
    } 
    catch (Exception) 
    { 
     obj.Dispose(); 
     throw; 
    } 
} 
11

Lý do không bao giờ được gọi là vì cách bạn phân bổ. Biến "tmp" không bao giờ được phân bổ ở tất cả, bởi vì hàm GetDisposable(bool) không bao giờ trả về do thực tế là bạn đã ném một ngoại lệ.

Nếu bạn đã nói thay cho những điều sau đây,

using (var tmp = new Disposable()) 
{ 
    throw new ArgumentException("Blah"); 
} 

sau đó bạn sẽ thấy rằng IDisposable::Dispose()không thực sự được gọi.

Điều cơ bản cần hiểu là khối using phải có tham chiếu hợp lệ đối tượng IDisposable. Nếu một số ngoại lệ xảy ra sao cho biến được khai báo trong khối using không được gán thì bạn sẽ không may mắn vì khối using sẽ không có kiến ​​thức về đối tượng IDisposable.

Đối với trả lại một đối tượng IDisposable từ một hàm, bạn nên sử dụng một catch khối tiêu chuẩn bên trong của hàm gọi Dispose() trong trường hợp của một thất bại, nhưng rõ ràng là bạn không nên sử dụng một khối using vì điều này sẽ xử lý các đối tượng trước khi bạn sẵn sàng tự làm như vậy.

+0

là chính xác. Nếu phương thức tạo IDisposable có thể thất bại trước khi trả về tham chiếu, nó có trách nhiệm đảm bảo rằng việc vứt bỏ được gọi. – ScottS

1

Giao diện IDisposable chỉ bảo đảm rằng lớp thực hiện nó có phương thức Vứt bỏ. Nó không có gì liên quan đến việc gọi phương thức này. Một khối sử dụng sẽ gọi Dispose trên đối tượng khi khối được thoát.

+0

Có một tình huống mà 'IDisposable' là tùy chọn, nhưng nếu được thực thi sẽ khiến' Dispose' được gọi tự động: nếu kiểu trả về của hàm 'GetEnumerator' được gọi là C#' foreach' hoặc vb.net 'For Each' câu lệnh thực hiện 'IDisposable', hoặc nếu kiểu trả về là' IEnumerator' và thể hiện nó trả về 'IDisposable', trình biên dịch sẽ gọi' IDisposable.Dispose' sau khi liệt kê xong. – supercat

+0

@supercat: đó là điều tốt để biết; Tôi đã không nhận ra rằng đây là trường hợp. –

+0

Đó là một chút khó khăn. Nếu kiểu trả về là một lớp * không thực hiện 'IDisposable', nhưng' GetEnumerator' trả về một lớp dẫn xuất thực hiện 'IDisposable', phương thức' Dispose' sẽ không được gọi, nhưng nếu nó là một giao diện giống như non-generic 'IEnumerator', trình biên dịch sẽ thấy rằng đối tượng trả về có thể thực hiện' IDisposable' và kiểm tra nó tại thời gian chạy. Một thiết kế xấu xí, nhưng vì 'Dispose' được bỏ qua từ 'IEnumerator', không có bất kỳ sự thay thế nào khác. – supercat

1

Bạn tạo một IDisposable trong GetDisposable nhưng kể từ khi bạn thoát khỏi chức năng bằng cách ném một ngoại lệ, nó không bao giờ được trả lại và do đó tmp không bao giờ được chỉ định. Tuyên bố sử dụng là viết tắt của

var tmp = UsingTest.GetDisposable(true); 
try { } 
finally 
{ 
    if(tmp != null) tmp.Dispose(); 
} 

và bạn không bao giờ đến được khối thử.Các giải pháp trong ví dụ của bạn là để kiểm tra error cờ trước khi tạo obj dùng một lần:

public static Disposable GetDisposable(bool error) 
{ 
    if (error) 
     throw new Exception("Error!"); 
    return new Disposable(); 
} 
+0

Không; không thành công trong điều kiện tôi đặt trên mã. – palswim

3

này là vì biến tmp không bao giờ được gán. Đó là một cái gì đó bạn cần phải cẩn thận với các đối tượng dùng một lần. Một định nghĩa tốt hơn cho GewtDisposable sẽ là:

public static Disposable GetDisposable(bool error) 
{ 
    var obj = new Disposable(); 

    try 
    { 
     if (error) 
      throw new Exception("Error!"); 
     return obj; 
    } 
    catch 
    { 
     obj.Dispose(); 
     throw; 
    } 
} 

Bởi vì nó đảm bảo rằng obj được xử lý.

+0

Một cải tiến nhỏ sẽ là sao chép biến đối tượng chính của bạn sang biến thứ hai và bỏ trống biến thứ nhất trước khi trả về lần thứ hai và sau đó sử dụng khối "cuối cùng" thay vì "bắt" để xử lý việc hủy (nếu biến là null, không vứt bỏ nó). Điều này sẽ đảm bảo các ngoại lệ cho thấy như đã xảy ra ở vị trí thích hợp, không phải lúc rethrow. – supercat

0

Câu hỏi liên quan là Handling iDisposable in failed initializer or constructor và tôi nghĩ câu trả lời là nếu bạn muốn tránh rò rỉ các đối tượng dùng một lần từ một nhà xây dựng không thành công, bạn sẽ phải buôn lậu một bản sao của đối tượng từ hàm khởi tạo (ví dụ: được chuyển vào thùng chứa, hoặc gán nó cho một biến được tham chiếu) và bọc cuộc gọi hàm tạo trong một khối catch. Icky, nhưng tôi không biết cách làm tốt hơn. VB.net thực sự có thể quản lý tốt hơn một chút so với C# vì cách khởi tạo của nó hoạt động.

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