2011-09-17 46 views
11

Có hợp pháp để gọi phương thức trên đối tượng được xử lý không? Nếu đúng thì tại sao?Tại sao đối tượng được xử lý không ném ngoại lệ khi sử dụng nó sau khi xử lý?

Trong chương trình giới thiệu sau đây, tôi đã là một lớp dùng một lần A (mà thực hiện giao diện IDisposable) .as xa như tôi biết, nếu tôi vượt qua đối tượng dùng một lần để using() xây dựng, sau đó Dispose() phương pháp được gọi tự động vào khung bế mạc:

A a = new A(); 
using (a) 
{ 
    //... 
}//<--------- a.Dispose() gets called here! 

//here the object is supposed to be disposed, 
//and shouldn't be used, as far as I understand. 

Nếu điều đó là đúng, sau đó hãy giải thích kết quả của chương trình này:

public class A : IDisposable 
{ 
    int i = 100; 
    public void Dispose() 
    { 
     Console.WriteLine("Dispose() called"); 
    } 
    public void f() 
    { 
     Console.WriteLine("{0}", i); i *= 2; 
    } 
} 

public class Test 
{ 
     public static void Main() 
     { 
       A a = new A(); 
       Console.WriteLine("Before using()"); 
       a.f(); 
       using (a) 
       { 
        Console.WriteLine("Inside using()"); 
        a.f(); 
       } 
       Console.WriteLine("After using()"); 
       a.f(); 
     } 
} 

Output (ideone):

Before using() 
100 
Inside using() 
200 
Dispose() called 
After using() 
400 

Tôi có thể gọi f() trên đối tượng được xử lý a? Điều này có được phép không? Nếu có, thì tại sao? Nếu không, thì tại sao chương trình trên không đưa ra ngoại lệ trong thời gian chạy?


Tôi biết rằng cấu trúc phổ biến của việc sử dụng using là thế này:

using (A a = new A()) 
{ 
    //working with a 
} 

Nhưng tôi chỉ thử nghiệm, đó là lý do tại sao tôi đã viết nó khác nhau.

+3

Tôi thấy ai đó thiếu tính chất xác định của quản lý bộ nhớ trong C++. :) – ChaosPandion

+7

Vì vậy, những gì bạn đang nói là: Tôi đã viết một chương trình không thực hiện các hợp đồng của các đối tượng dùng một lần, và khi tôi chạy nó, nó không thực hiện hợp đồng của các đối tượng dùng một lần. ** Bạn ** chịu trách nhiệm thực hiện hành vi đó. Bạn đã không làm điều đó. Đi làm đi. –

Trả lời

11

Xử lý không có nghĩa là đã biến mất. Xử lý chỉ có nghĩa là bất kỳ tài nguyên không được quản lý nào (như một tệp, kết nối của bất kỳ loại nào, ...) đã được phát hành. Mặc dù điều này thường có nghĩa là đối tượng không cung cấp bất kỳ chức năng hữu ích nào, vẫn có thể có các phương thức không phụ thuộc vào tài nguyên không được quản lý đó và vẫn hoạt động như bình thường.

Cơ chế xử lý tồn tại dưới dạng .net (và kế thừa, C# .net) là môi trường được thu gom rác, có nghĩa là bạn không chịu trách nhiệm về quản lý bộ nhớ. Tuy nhiên, bộ thu gom rác không thể quyết định liệu một tài nguyên không được quản lý có được sử dụng hay không, do đó bạn cần tự làm điều này.

Nếu bạn muốn phương pháp để ném một ngoại lệ sau khi đối tượng đã được diposed, bạn sẽ cần một boolean để nắm bắt tình trạng dispose, và một khi đối tượng được xử lý, bạn ném một ngoại lệ:

public class A : IDisposable 
{ 
    int i = 100; 
    bool disposed = false; 
    public void Dispose() 
    { 
     disposed = true; 
     Console.WriteLine("Dispose() called"); 
    } 
    public void f() 
    { 
     if(disposed) 
     throw new ObjectDisposedException(); 

     Console.WriteLine("{0}", i); i *= 2; 
    } 
} 
+0

Đã xử lý * nên * có nghĩa là mọi tài nguyên không được quản lý đều được phát hành. Nhưng nó không phải, nếu lớp học được triển khai kém. – svick

+0

Rõ ràng. Nhưng đó là trường hợp với bất kỳ hành vi quan trọng, tôi giả định trường hợp lý tưởng. – Femaref

+0

Nhưng theo mô hình vứt bỏ Vứt bỏ có nghĩa là cả tài nguyên được quản lý và không được quản lý đều được giải phóng khi được gọi từ khách hàng bên ngoài. Trong trường hợp đó tôi mong đợi không có phương pháp để làm việc. đúng? – YakRangi

6

Ngoại lệ không được ném vì bạn chưa thiết kế các phương pháp để ném ObjectDisposedException sau khi Dispose đã được gọi.

Các clr không tự động biết rằng nó sẽ ném ObjectDisposedException khi Vứt bỏ được gọi. Đó là trách nhiệm của bạn để ném một ngoại lệ nếu Vứt bỏ đã phát hành bất kỳ nguồn lực cần thiết để thực hiện thành công các phương pháp của bạn.

2

Một disposer trong C# không giống như một destructor trong C++. Một disposer được sử dụng để phát hành các tài nguyên được quản lý (hoặc không được quản lý) trong khi đối tượng vẫn hợp lệ.

Ngoại lệ được ném tùy thuộc vào việc triển khai lớp học. Nếu f() không yêu cầu sử dụng các đối tượng đã được xử lý của bạn, thì nó không nhất thiết phải ném một ngoại lệ.

3

Một điển hình Dispose() thực hiện chỉ gọi Dispose() trên bất kỳ đối tượng nào mà nó lưu trữ trong các trường của nó dùng một lần. Mà lần lượt phát hành tài nguyên không được quản lý.Nếu bạn thực hiện IDisposable và không thực sự làm bất cứ điều gì, giống như bạn đã làm trong đoạn mã của bạn, thì trạng thái đối tượng không thay đổi chút nào. Không có gì có thể đi sai. Đừng trộn lẫn với việc hoàn thành.

2

Gọi hàm Dispose() không đặt tham chiếu đối tượng thành rỗng và lớp dùng một lần tùy chỉnh của bạn không chứa bất kỳ logic nào để ném ngoại lệ nếu chức năng của nó được truy cập sau khi Dispose() đã được gọi hợp pháp.

Trong thế giới thực, Dispose() phát hành tài nguyên không được quản lý và các tài nguyên đó sẽ không có sẵn sau đó, và/hoặc tác giả lớp đã ném ObjectDisposedException nếu bạn cố gắng sử dụng đối tượng sau khi gọi Dispose(). Thông thường, một boolean mức lớp sẽ được đặt thành true trong phần Dispose() và giá trị đó được kiểm tra trong các thành viên khác của lớp trước khi chúng thực hiện bất kỳ công việc nào, với ngoại lệ bị ném nếu bool là true.

2

Mục đích của IDisposable là cho phép đối tượng sửa trạng thái của bất kỳ thực thể bên ngoài nào, vì lợi ích của nó, được đưa vào trạng thái ít lý tưởng cho các mục đích khác. Ví dụ, một đối tượng Io.Ports.SerialPort có thể đã thay đổi trạng thái của một cổng nối tiếp từ "có sẵn cho bất kỳ ứng dụng nào muốn nó" thành "chỉ có thể sử dụng bởi một đối tượng Io.Ports.SerialPort cụ thể"; mục đích chính của SerialPort.Dispose là khôi phục trạng thái của cổng nối tiếp thành "khả dụng cho bất kỳ ứng dụng nào". Tất nhiên, một khi một đối tượng thực hiện IDisposable đã thiết lập lại các thực thể đã duy trì một trạng thái nhất định vì lợi ích của nó, nó sẽ không còn có lợi ích của trạng thái duy trì của các thực thể đó nữa. Ví dụ, một khi trạng thái của cổng nối tiếp đã được đặt thành "khả dụng cho bất kỳ ứng dụng nào", các luồng dữ liệu mà nó đã được liên kết không còn có thể được sử dụng để gửi và nhận dữ liệu nữa. Nếu một đối tượng có thể hoạt động bình thường mà không có các thực thể bên ngoài được đưa vào trạng thái đặc biệt vì lợi ích của nó, sẽ không có lý do gì để rời khỏi các thực thể bên ngoài trong một trạng thái đặc biệt ngay từ đầu.

Nói chung, sau khi IDisposable.Dispose đã được gọi trên một đối tượng, đối tượng không được mong đợi có khả năng làm được nhiều. Cố gắng sử dụng hầu hết các phương pháp trên một đối tượng như vậy sẽ chỉ ra một lỗi; nếu một phương thức không thể được mong đợi hợp lý để làm việc, thì cách thích hợp để chỉ ra rằng đó là thông qua ObjectDisposedException.

Microsoft gợi ý rằng gần như tất cả các phương pháp trên một đối tượng thực hiện IDisposable nên ném ObjectDisposedException nếu chúng được sử dụng trên một đối tượng đã được xử lý. Tôi xin đề nghị lời khuyên như vậy là quá mức. Nó thường rất hữu ích cho các thiết bị để lộ các phương pháp hoặc các thuộc tính để tìm hiểu những gì đã xảy ra trong khi đối tượng còn sống. Mặc dù người ta có thể cung cấp cho một lớp truyền thông một phương pháp Close cũng như một phương pháp Dispose, và chỉ cho phép một truy vấn những thứ như NumberOfPacketsExchanged sau khi đóng nhưng không sau một Dispose, nhưng điều đó có vẻ quá phức tạp. Đọc tài sản liên quan đến những điều đã xảy ra trước khi một đối tượng đã được xử lý có vẻ như một mô hình hoàn toàn hợp lý.

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