2009-06-19 35 views
18

Một vài ngày trước, tôi đăng tải một số mã như thế này:Có cần thiết phải bao bọc StreamWriter trong một khối sử dụng không?

StreamWriter writer = new StreamWriter(Response.OutputStream); 
writer.WriteLine("col1,col2,col3"); 
writer.WriteLine("1,2,3"); 
writer.Close(); 
Response.End(); 

tôi đã nói rằng thay vì tôi nên quấn StreamWriter trong một khối sử dụng trong trường hợp ngoại lệ. Thay đổi như vậy sẽ làm cho nó trông như thế này:

using(StreamWriter writer = new StreamWriter(Response.OutputStream)) 
{ 
    writer.WriteLine("col1,col2,col3"); 
    writer.WriteLine("1,2,3"); 
    writer.Close(); //not necessary I think... end of using block should close writer 
} 
Response.End(); 

Tôi không chắc chắn tại sao đây là một thay đổi có giá trị. Nếu một ngoại lệ xảy ra mà không có khối sử dụng, người viết và phản hồi sẽ vẫn được dọn dẹp, đúng không? Khối sử dụng đạt được gì cho tôi?

Trả lời

21

Nope luồng sẽ vẫn mở trong ví dụ đầu tiên, vì lỗi sẽ phủ nhận việc đóng.

Toán tử sử dụng buộc gọi Dispose() được cho là để làm sạch đối tượng lên và đóng tất cả các kết nối đang mở khi nó thoát khỏi khối.

3

Nếu trường hợp ngoại lệ xảy ra mà không có khối sử dụng và giết chương trình, bạn sẽ bị ngắt kết nối. Khối sử dụng sẽ luôn đóng kết nối cho bạn, tương tự như khi bạn sử dụng try {} catch {} cuối cùng {}

+1

Nếu chương trình thoát, tay cầm sẽ được làm sạch. Câu lệnh sử dụng chỉ giúp trong khi ứng dụng đang chạy. –

+0

finalizers là thuật ngữ cho những gì làm sạch trên lối ra. – Guvante

1

Theo ý kiến ​​của tôi, cần phải bọc bất kỳ lớp nào triển khai IDisposable trong khối sử dụng. Thực tế là một lớp thực hiện IDisposable có nghĩa là lớp đó có các tài nguyên cần được làm sạch.

+1

Nếu bạn cần duy trì trạng thái mở đó trong một khoảng thời gian dài hơn. Chẳng hạn như ổ cắm và dòng Async. Có một lý do bạn có thể tự mình tạo ra IDisposible. Ngoài ra IDisposable cũng sẽ đóng luồng. Có nghĩa là nếu bạn chuyển hướng nội dung chẳng hạn như từ một người viết luồng như trên và vứt bỏ người viết luồng thì người viết luồng sẽ đóng luồng được truyền cho điều này. điều này có thể ngăn không cho dữ liệu được gửi đến máy khách. –

0

sử dụng các cuộc gọi chặn bỏ() khi kết thúc. Nó chỉ là một cách tiện dụng để đảm bảo rằng các nguồn lực được làm sạch một cách kịp thời.

+0

Quan niệm sai lầm phổ biến, Vứt bỏ là một cách để nói với người tiêu dùng về lớp học của bạn rằng đối tượng cần được dọn dẹp, Finalizers là cách mà bộ thu gom rác dọn sạch khi đối tượng rời khỏi phạm vi hoặc chương trình kết thúc. – Guvante

+0

http://msdn.microsoft.com/en-us/library/system.idisposable.dispose(VS.71).aspx "Sử dụng phương pháp này để đóng hoặc giải phóng các tài nguyên không được quản lý như tệp, luồng và xử lý được giữ bởi Ví dụ của lớp thực hiện giao diện này. Phương thức này, theo quy ước, được sử dụng cho tất cả các tác vụ liên quan đến giải phóng tài nguyên được tổ chức bởi một đối tượng hoặc chuẩn bị một đối tượng để tái sử dụng. " – Robert

0

Trong hầu hết mọi trường hợp, nếu một lớp thực hiện IDisposable, và nếu bạn đang tạo một thể hiện của lớp đó, thì bạn cần khối sử dụng.

+0

@ John - có ngoại lệ nào không? –

+0

Chỉ có proxy máy khách WCF, theo như tôi biết. Điều này là do lỗi thiết kế trên một phần của Microsoft. –

+0

@John: làm thế nào để bạn hình dung đây là lỗi? Có những lúc bạn cần để hơi thở ra ngoài mở và chỉ đóng nó khi khách hàng hoàn tất. Nếu bạn đóng nó trước khi khách hàng tải xuống tất cả các dữ liệu kết nối sẽ bị hủy bỏ và khách hàng sẽ không bao giờ nhận được dữ liệu –

3

Cuối cùng, người viết sẽ được dọn dẹp. Khi điều này xảy ra là tùy thuộc vào bộ thu gom rác, người sẽ nhận thấy rằng Dispose cho lệnh chưa được gọi và gọi nó. Tất nhiên, GC có thể không chạy trong vài phút, vài giờ hoặc vài ngày tùy theo tình hình. Nếu người viết đang nắm giữ một khóa độc quyền trên nói, một tập tin, không có quá trình khác sẽ có thể mở nó, mặc dù bạn đã hoàn thành lâu dài.

Khối sử dụng đảm bảo cuộc gọi Dispose luôn được thực hiện và do đó luôn luôn gọi Close, bất kể dòng điều khiển nào xảy ra.

+0

Làm thế nào để nó sạch? Liệu nó gọi phương thức Vứt bỏ riêng của nó trong một finalizer? – xyz

+0

Thông thường đối với các tham chiếu thành viên đối với các tài nguyên không được quản lý, cùng một mã làm sạch sẽ được gọi từ cả hai. Tôi đã nói "Gọi nó" ở trên, nhưng không chắc chắn rằng hàm Dispose được gọi. Hành vi này có thể sẽ rất giống nhau trong cả hai trường hợp. –

+0

GC sẽ * không bao giờ * gọi phương thức Vứt bỏ của đối tượng. Nếu đối tượng có một finaliser thì cuối cùng nó sẽ được gọi bởi GC, và có thể là finaliser sau đó có thể tự gọi Dispose (cho dù nó có hay không là một chi tiết thực hiện). – LukeH

3

Bao bì các StreamWriter trong một khối using là khá nhiều tương đương với đoạn mã sau:

StreamWriter writer; 
try 
{ 
    writer = new StreamWriter(Response.OutputStream); 
    writer.WriteLine("col1,col2,col3"); 
    writer.WriteLine("1,2,3"); 
} 
catch 
{ 
    throw; 
} 
finally 
{ 
    if (writer != null) 
    { 
     writer.Close();  
    } 
} 

Trong khi bạn rất tốt có thể viết mã này chính mình, nó là như vậy dễ dàng hơn để chỉ cần đặt nó trong một khối sử dụng .

+1

đóng StreamWriter sẽ clode các Respose .OutputStream là tốt. Chỉ là một cảnh báo nếu bạn tìm thấy bạn dữ liệu không làm cho nó cho khách hàng. –

+1

Chỉ cần bỏ qua phần catch() {}, nó không phục vụ mục đích gì và chỉ có thể gây nhầm lẫn. –

1

Quy tắc chung của tôi là, nếu tôi thấy Vứt bỏ được liệt kê trong intellisense, tôi bọc nó trong một khối sử dụng.

+0

Quy tắc tốt, nhưng không đủ. Một số lớp sử dụng triển khai rõ ràng IDisposable, ẩn hiệu quả Dispose(). Ý tưởng tồi, nhưng nó đang được sử dụng. Bạn vẫn cần phải áp dụng bằng() {} –

0

Trong khi thực hành tốt là luôn luôn nhúng các lớp dùng một lần như StreamWriter, như những người khác chỉ ra, trong trường hợp này không quan trọng.

Response.OutputStream sẽ được xử lý bởi cơ sở hạ tầng ASP.NET khi nó xử lý xong yêu cầu của bạn.

StreamWriter giả định nó "sở hữu" Luồng được truyền tới hàm tạo và do đó sẽ đóng luồng khi được xử lý.Nhưng trong mẫu bạn cung cấp, luồng đã được khởi tạo bên ngoài mã của bạn, vì vậy sẽ có một chủ sở hữu khác chịu trách nhiệm dọn dẹp.

+0

Còn StreamWriter thì sao? Điều gì sẽ xảy ra nếu nó được giữ trên một số tài nguyên không được quản lý ngoài luồng mà nó kết thúc tốt đẹp? –

+0

Nó không có bất kỳ tài nguyên không được quản lý nào khác. Nhưng tôi đồng ý rằng đó là thực hành tốt để xử lý nó, tôi chỉ chỉ ra rằng trong tình huống này, dòng sẽ được xử lý theo cách nào đó. – Joe

+0

Nếu bạn không loại bỏ StreamWriter, bạn cần phải gọi Flush() khi bạn đã hoàn tất. Nếu không, một phần đầu ra của bạn vẫn có thể được lưu vào bộ nhớ và chưa được ghi vào luồng. –

16

Tôi sẽ đưa ra ý kiến ​​bất đồng ý kiến. Câu trả lời cho câu hỏi cụ thể "Có cần thiết phải bao bọc StreamWriter trong một khối sử dụng không?" Trên thực tế, bạn không được cuộc gọi Vứt bỏ trên một StreamWriter, bởi vì Vứt bỏ nó được thiết kế tồi và làm điều sai trái.

Sự cố với StreamWriter là, khi bạn Vứt bỏ nó, nó sẽ loại bỏ luồng cơ bản. Nếu bạn đã tạo StreamWriter với tên tệp, và nó đã tạo ra một FileStream riêng trong nội bộ, thì hành vi này sẽ hoàn toàn thích hợp. Nhưng nếu, như ở đây, bạn đã tạo StreamWriter với luồng hiện tại, thì hành vi này hoàn toàn là Điều sai (tm). Nhưng nó vẫn làm.

Mã như thế này sẽ không làm việc:

var stream = new MemoryStream(); 
using (var writer = new StreamWriter(stream)) { ... } 
stream.Position = 0; 
using (var reader = new StreamReader(stream)) { ... } 

vì khi using khối của StreamWriter disposes StreamWriter, mà sẽ lần lượt vứt bỏ con suối. Vì vậy, khi bạn cố gắng đọc từ luồng, bạn sẽ nhận được một ObjectDisposedException.

StreamWriter là vi phạm nghiêm trọng quy tắc "dọn dẹp mớ hỗn độn của riêng bạn". Nó cố gắng làm sạch đống lộn xộn của người khác, cho dù họ có muốn hay không.

(Hãy tưởng tượng nếu bạn thử điều này trong thực tế đời sống. Hãy thử giải thích với cảnh sát tại sao bạn đã đột nhập vào nhà của người khác và bắt đầu ném tất cả các công cụ của họ vào thùng rác ...)

Vì lý do đó, tôi xem xét StreamWriter (và StreamReader, mà làm điều tương tự) là một trong số rất ít các lớp học, nơi "nếu nó thực hiện IDisposable, bạn nên gọi Dispose" là sai. Không bao giờ gọi Vứt bỏ trên StreamWriter được tạo trên luồng hiện có. Gọi Flush() để thay thế.

Sau đó, chỉ cần đảm bảo bạn dọn sạch Luồng khi cần. (Như Joe đã chỉ ra, ASP.NET phân phối Response.OutputStream cho bạn, vì vậy bạn không cần phải lo lắng về nó ở đây.)

Cảnh báo: nếu bạn không loại bỏ StreamWriter, thì bạn do cần gọi Flush() khi bạn viết xong. Nếu không, bạn có thể có dữ liệu vẫn đang được đệm trong bộ nhớ mà không bao giờ làm cho nó vào dòng đầu ra.

Quy tắc của tôi cho StreamReader là, giả vờ rằng nó không triển khai IDisposable. Chỉ cần để nó đi khi bạn đã hoàn tất.

Quy tắc của tôi cho StreamWriter là, hãy gọi Dọn dẹp nơi bạn có thể gọi là Vứt bỏ. (Điều này có nghĩa là bạn phải sử dụng số try .. finally thay vì using.)

+0

"Quy tắc của tôi cho StreamReader là, giả sử nó không triển khai IDisposable" - nhưng không phải nếu bạn đã xây dựng StreamReader bằng tên tệp vì nó sở hữu luồng trong trường hợp này. Tôi không đồng ý rằng StreamReader/Writer được thiết kế kém. Mặc dù tôi hiểu các vấn đề bạn mô tả, nhưng tất cả thiết kế đều được cân nhắc và tôi nghĩ cân bằng hành vi là đúng đối với hầu hết các trường hợp sử dụng. Chắc chắn trong ví dụ của bạn ở trên, bây giờ bạn phải làm tổ của bạn bằng cách sử dụng báo cáo, nhưng nó không phải là một việc lớn như vậy. Có một số thảo luận về trách nhiệm xử lý trong câu hỏi này: http://stackoverflow.com/questions/674879 – Joe

+0

@Joe: Quy tắc chống của bạn quá mạnh. Nó sẽ được, không gọi Dispose (do đó không thực hiện một khối sử dụng) trên StreamReader/StreamWriter trừ khi bạn có ý định đóng luồng cơ bản. Có lẽ những gì cần thiết là một cái gì đó giống như cờ CloseInput trên XmlReaderSettings, nhưng thay vì cho StreamReader. –

+0

@Joe: Đúng vậy. Những gì họ nên * đã làm là, nếu bạn xây dựng nó với một tên tập tin, sau đó các nhà xây dựng tạo ra một dòng và Vứt bỏ đóng cửa nó; nhưng nếu bạn xây dựng nó bằng một luồng, nó không đóng nó, bởi vì ai đó đang sở hữu nó. –

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