Điều gì xảy ra nếu tôi quay lại trước khi kết thúc sử dụng câu lệnh? Liệu việc xử lý có được gọi không?


115

Tôi có mã sau

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

Các dispose()phương pháp được gọi là ở phần cuối của usingcâu niềng răng }phải không? Vì tôi returntrước khi kết thúc usingcâu lệnh, MemoryStreamđối tượng sẽ được xử lý đúng cách? chuyện gì xảy ra ở đây thế?


4
@JonH: Tìm bản sao chính xác, sau đó bỏ phiếu để đóng trong trường hợp đó.
Noldorin

@Noldorin: Tôi đã tìm kiếm một bản dupe về điều này, bởi vì tôi nghĩ rằng nó hẳn đã được hỏi trước đó, nhưng tôi không thể tìm thấy. Tôi đoán rằng vẫn có những câu hỏi dễ dàng ngoài kia. :)
Randolpho

@JonH và @Noldorin - các bản sao sẽ được đưa ra khi câu hỏi được hình thành, nó tìm kiếm "các câu hỏi tương tự", một tính năng mà mọi người dường như không sử dụng đủ.
Adam Houldsworth

@Adam: tự mình thử đi. Sao chép / dán tiêu đề và xem những gì trùng lặp được hệ thống trình bày. Tôi sẽ cho bạn một gợi ý: câu trả lời là không. Ditto nếu bạn tìm kiếm qua Google hoặc tìm kiếm của SO. Có vẻ như câu hỏi này chưa được hỏi trước đây.
Randolpho

Aaap ... Tôi rút lại. Tôi vừa tìm thấy một bản sao gần như trùng lặp, sau một số tìm kiếm rất chuyên dụng: stackoverflow.com/questions/2641692/… Bây giờ, câu hỏi được hỏi hoàn toàn khác, nhưng câu hỏi cuối cùng thì khá giống nhau. Tôi cho rằng chúng ta có thể coi đây là một bản dupe.
Randolpho

Câu trả lời:


167

Có, Disposesẽ được gọi. Nó được gọi ngay khi việc thực thi rời khỏi phạm vi của usingkhối, bất kể phương tiện nào để rời khỏi khối, có thể là khi kết thúc quá trình thực thi khối, một returncâu lệnh hay một ngoại lệ.

Như @Noldorin đã chỉ ra một cách chính xác, việc sử dụng một usingkhối trong mã sẽ được biên dịch thành try/ finally, với Disposeviệc được gọi trong finallykhối. Ví dụ đoạn mã sau:

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

hiệu quả trở thành:

MemoryStream ms = new MemoryStream();
try
{
    // code
    return 0;
}
finally
{
    ms.Dispose();
}

Vì vậy, bởi vì finallyđược đảm bảo thực thi sau khi trykhối kết thúc thực thi, bất kể đường dẫn thực thi của nó Disposelà gì , được đảm bảo sẽ được gọi, bất kể điều gì.

Để biết thêm thông tin, hãy xem bài viết MSDN này .

Phụ lục:
Chỉ cần lưu ý một chút: vì Disposeđược đảm bảo sẽ được gọi, nên hầu như luôn luôn là một ý kiến ​​hay để đảm bảo rằng Disposekhông bao giờ có ngoại lệ khi bạn triển khai IDisposable. Thật không may, có một số lớp trong thư viện lõi có thể xảy ra trong một số trường hợp nhất định khi Disposeđược gọi - Tôi đang nhìn bạn, WCF Service Reference / Client Proxy! - và khi điều đó xảy ra, có thể rất khó để theo dõi ngoại lệ ban đầu nếu Disposeđược gọi trong thời gian thư giãn ngăn xếp ngoại lệ, vì ngoại lệ ban đầu bị nuốt có lợi cho ngoại lệ mới được tạo bởi Disposecuộc gọi. Nó có thể gây thất vọng tột độ. Hay đó là sự bực bội điên cuồng? Một trong hai. Có thể là cả hai.


4
Tôi nghĩ bạn sẽ thấy nó được biên dịch một cách hiệu quả thành một khối try-last với lệnh gọi đến Disposecuối cùng, vì vậy nó hoạt động hiệu quả trong việc triển khai finally, như bạn mô tả.
Noldorin

@Noldorin: chính xác. Mặc dù tôi cho rằng tôi có thể rõ ràng về điều đó. Sửa sắp tới ....
Randolpho

1
Cũng lưu ý rằng có một số trường hợp trong đó khối cuối cùng không được đảm bảo thực thi, chẳng hạn như sử dụng Environment.FailFast và nếu một StackOverFlowException xảy ra.
Christopher McAtackney,

@ C.McAtackney: cũng là một điểm tốt. Ngoài ra, IIRC, OutOfMemoryException; về cơ bản nếu bạn không thể bắt được ngoại lệ vì đó là một lỗi thực thi nghiêm trọng, thì Dispose sẽ không được gọi. Tất nhiên, trong trường hợp như vậy, chương trình được đảm bảo sẽ gặp sự cố, cùng với bất kỳ bộ nhớ nào được cấp cho nó, vì vậy trong 99,9% trường hợp, đó không phải là vấn đề, trừ khi bạn đang làm những việc khó khăn như ghi vào một tệp trong phương pháp xử lý của bạn . Ngoài sự cố chương trình thảm khốc, đó là.
Randolpho

Bạn không nên sử dụng câu lệnh 'using ()' với WCF - hãy tham khảo bài viết này để biết thêm thông tin. Đây là đoạn mã tôi sử dụng cho các proxy WCF: 'WCFProxy variableName = null; thử {biếnName = new WCFProxy (); // Mã CÔNG VIỆC ở đây variableName.Proxy.Close (); biếnName.Dispose (); } catch (Exception) {if (variableName! = null && variableName.Proxy! = null) {variableName.Proxy.Abort (); } phi; } '
Dave Black

18

usingcác câu lệnh hoạt động chính xác như try ... finallycác khối, vì vậy sẽ luôn thực thi trên bất kỳ đường dẫn thoát mã nào. Tuy nhiên, tôi tin rằng chúng phải tuân theo những trường hợp rất hiếm và hiếm gặp trong đó finallycác khối không được gọi. Một ví dụ mà tôi có thể nhớ là nếu luồng nền trước thoát ra trong khi các luồng nền đang hoạt động: tất cả các luồng ngoài GC đều bị tạm dừng, nghĩa là finallycác khối không được chạy.

Chỉnh sửa rõ ràng: chúng hoạt động giống nhau ngoài logic cho phép chúng xử lý các đối tượng IDisposable, d'oh.

Nội dung thưởng: chúng có thể được xếp chồng lên nhau (nếu các loại khác nhau):

using (SqlConnection conn = new SqlConnection("string"))
using (SqlCommand comm = new SqlCommand("", conn))
{

}

Và cũng được phân tách bằng dấu phẩy (trong đó các loại giống nhau):

using (SqlCommand comm = new SqlCommand("", conn), 
       SqlCommand comm2 = new SqlCommand("", conn))
{

}

4

Đối tượng MemoryStream của bạn sẽ được xử lý đúng cách, không cần phải lo lắng về điều đó.



0

Hãy xem mã của bạn trong phản xạ sau khi bạn biên dịch nó. Bạn sẽ thấy rằng trình biên dịch sẽ cấu trúc lại mã để đảm bảo rằng việc xử lý được gọi trên luồng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.