Tôi nên gọi Close () hoặc Dispose () cho các đối tượng truyền phát?


151

Lớp học như Stream, StreamReader, StreamWritervv cụ IDisposablegiao diện. Điều đó có nghĩa là, chúng ta có thể gọi Dispose()phương thức trên các đối tượng của các lớp này. Họ cũng đã định nghĩa một publicphương thức gọi là Close(). Bây giờ điều đó làm tôi bối rối, liệu tôi nên gọi cái gì khi tôi hoàn thành với đồ vật? Nếu tôi gọi cả hai thì sao?

Mã hiện tại của tôi là:

using (Stream responseStream = response.GetResponseStream())
{
   using (StreamReader reader = new StreamReader(responseStream))
   {
      using (StreamWriter writer = new StreamWriter(filename))
      {
         int chunkSize = 1024;
         while (!reader.EndOfStream)
         {
            char[] buffer = new char[chunkSize];
            int count = reader.Read(buffer, 0, chunkSize);
            if (count != 0)
            {
               writer.Write(buffer, 0, count);
            }
         }
         writer.Close();
      }
      reader.Close();
   }
}

Như bạn thấy, tôi đã viết các using()cấu trúc, tự động gọi Dispose()phương thức trên từng đối tượng. Nhưng tôi cũng gọi Close()phương thức. Đúng không?

Vui lòng gợi ý cho tôi các thực tiễn tốt nhất khi sử dụng các đối tượng truyền phát. :-)

Ví dụ MSDN không sử dụng using()cấu trúc và Close()phương thức gọi :

Liệu nó có tốt không?


Nếu bạn đang sử dụng ReSharper, bạn có thể định nghĩa đây là một "phản mẫu" trong danh mục sản phẩm. ReSharper sẽ đánh dấu mỗi lần sử dụng là lỗi / gợi ý / cảnh báo liên quan đến định nghĩa của bạn. Cũng có thể định nghĩa ReSharper phải áp dụng QuickFix như thế nào cho sự cố này.
Thorsten Hans

3
Mẹo nhỏ {//..Một số mã}
Latrova


Bạn không cần lồng các câu lệnh bằng cách sử dụng các câu lệnh như thế, bạn có thể xếp chúng lên nhau và có một bộ dấu ngoặc. Trên một bài đăng khác, tôi đã đề xuất chỉnh sửa cho một đoạn mã nên sử dụng các câu lệnh với kỹ thuật đó nếu bạn muốn xem và sửa "mũi tên mã" của mình: stackoverflow.com/questions/5282999/iêu
Timothy Gonzalez

2
@ Suncat2000 Bạn có thể có nhiều câu lệnh sử dụng, nhưng không lồng chúng và thay vào đó xếp chồng chúng. Tôi không có nghĩa là cú pháp như thế này mà hạn chế các loại : using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Ý tôi là như thế này nơi bạn có thể xác định lại loại:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez

Câu trả lời:


101

Bước nhanh vào Reflector.NET cho thấy Close()phương thức trên StreamWriterlà:

public override void Close()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

StreamReaderlà:

public override void Close()
{
    this.Dispose(true);
}

Ghi Dispose(bool disposing)đè trong StreamReaderlà:

protected override void Dispose(bool disposing)
{
    try
    {
        if ((this.Closable && disposing) && (this.stream != null))
        {
            this.stream.Close();
        }
    }
    finally
    {
        if (this.Closable && (this.stream != null))
        {
            this.stream = null;
            /* deleted for brevity */
            base.Dispose(disposing);
        }
    }
}

Các StreamWriterphương pháp tương tự.

Vì vậy, đọc mã rõ ràng rằng bạn có thể gọi Close()Dispose()phát trực tuyến bao nhiêu lần tùy thích và theo bất kỳ thứ tự nào. Nó sẽ không thay đổi hành vi theo bất kỳ cách nào.

Vì vậy, nó liên quan đến việc có dễ đọc hơn hay không Dispose(), Close()và / hoặc using ( ... ) { ... }.

Sở thích cá nhân của tôi là using ( ... ) { ... }luôn luôn nên được sử dụng khi có thể vì nó giúp bạn "không chạy bằng kéo".

Nhưng, trong khi điều này giúp chính xác, nó làm giảm khả năng đọc. Trong C #, chúng ta đã có rất nhiều cách đóng dấu ngoặc nhọn, vậy làm thế nào để chúng ta biết cái nào thực sự đóng trên luồng?

Vì vậy, tôi nghĩ rằng tốt nhất là làm điều này:

using (var stream = ...)
{
    /* code */

    stream.Close();
}

Nó không ảnh hưởng đến hành vi của mã, nhưng nó hỗ trợ khả năng đọc.


20
" Trong C #, chúng tôi đã có rất nhiều cách đóng dấu ngoặc nhọn, vậy làm thế nào để chúng tôi biết cái nào thực sự đóng trên luồng? " Tôi không nghĩ rằng đây là một vấn đề lớn: Luồng được đóng "đúng lúc", tức là khi biến đi ra khỏi phạm vi và không còn cần thiết nữa.
Heinzi

110
Hmm, không, đó là một "tại sao anh ta lại đóng nó hai lần ??" tăng tốc trong khi đọc.
Hans Passant

57
Tôi không đồng ý với Close()cuộc gọi dư thừa . Nếu ai đó ít kinh nghiệm nhìn vào mã và không biết về usinganh ta sẽ: 1) tra cứu và tìm hiểu , hoặc 2) mù quáng thêm một Close()cách thủ công. Nếu anh ta chọn 2), có thể một số nhà phát triển khác sẽ thấy sự dư thừa Close()và thay vì "cười thầm", hãy hướng dẫn cho nhà phát triển ít kinh nghiệm hơn. Tôi không ủng hộ việc làm cho cuộc sống của các nhà phát triển thiếu kinh nghiệm trở nên khó khăn, nhưng tôi ủng hộ việc biến họ thành các nhà phát triển có kinh nghiệm.
R. Martinho Fernandes

14
Nếu bạn sử dụng bằng cách sử dụng + Đóng () và bật / phân tích, bạn sẽ nhận được "cảnh báo: CA2202: Microsoft.Usage: Object 'f' có thể được xử lý nhiều lần trong phương thức 'Foo (chuỗi)'. Để tránh tạo Hệ thống. ObjectDisposedException bạn không nên gọi Vứt bỏ nhiều lần trên một đối tượng.: Lines: 41 "Vì vậy, trong khi triển khai hiện tại vẫn ổn khi gọi Đóng và Loại bỏ, theo tài liệu và / phân tích, sẽ không ổn và có thể thay đổi trong các phiên bản tương lai. mạng lưới.
marc40000

4
+1 cho câu trả lời hay. Một điều khác để xem xét. Tại sao không thêm một nhận xét sau khi đóng dấu ngoặc nhọn như // Đóng hoặc như tôi làm, là một người mới, tôi thêm một lớp lót sau khi bất kỳ dấu ngoặc đóng nào không rõ ràng. ví dụ như trong một lớp dài, tôi sẽ thêm // Kết thúc không gian tên XXX sau lần đóng cuối cùng và // Kết thúc lớp YYY sau lần đóng cuối cùng thứ hai. Đây không phải là những gì bình luận là cho. Chỉ tò mò thôi. :) Là một người mới, tôi đã thấy mã như vậy, rất nhiều lý do tại sao tôi đến đây. Tôi đã đặt câu hỏi "Tại sao cần phải đóng lần thứ hai". Tôi cảm thấy thêm dòng mã không thêm vào cho rõ ràng. Lấy làm tiếc.
Francis Rodgers

51

Không, bạn không nên gọi những phương thức đó bằng tay. Ở cuối usingkhối, Dispose()phương thức được gọi tự động sẽ chăm sóc các tài nguyên không được quản lý miễn phí (ít nhất là đối với các lớp .NET BCL tiêu chuẩn như luồng, trình đọc / trình ghi, ...). Vì vậy, bạn cũng có thể viết mã của bạn như thế này:

using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
        using (StreamWriter writer = new StreamWriter(filename))
        {
            int chunkSize = 1024;
            while (!reader.EndOfStream)
            {
                 char[] buffer = new char[chunkSize];
                 int count = reader.Read(buffer, 0, chunkSize);
                 if (count != 0)
                 {
                     writer.Write(buffer, 0, count);
                 }
            }
         }

Các Close()phương pháp gọi Dispose().


1
Tôi khá chắc chắn rằng bạn không cần phải là usingngười đầu tiên responseStreamvì điều đó được bao bọc bởi readerđiều đó sẽ đảm bảo nó đóng khi người đọc bị loại bỏ. +1 không mất tiền
Isak Savo

Điều này thật khó hiểu khi bạn nói The Close method calls Dispose... và trong phần còn lại của bài đăng của bạn, bạn đang ngụ ý rằng Dispose()sẽ gọi Close(), tôi không nên gọi sau. Có phải bạn đang nói họ gọi nhau?
Nawaz

@Nawaz, bài viết của tôi thật khó hiểu. Phương thức Đóng chỉ đơn giản gọi Vứt bỏ. Trong trường hợp của bạn, bạn cần Vứt bỏ để giải phóng tài nguyên không được quản lý. Bằng cách gói mã của bạn bằng cách sử dụng câu lệnh, phương thức Vứt bỏ được gọi.
Darin Dimitrov

3
Câu trả lời khủng khiếp. Nó giả sử bạn có thể sử dụng một usingkhối. Tôi đang thực hiện một lớp viết theo thời gian và do đó không thể.
Jez

5
@Jez Lớp của bạn sau đó sẽ triển khai giao diện IDis Dùng một lần và cũng có thể Đóng () nếu đóng là thuật ngữ chuẩn trong khu vực , để các lớp sử dụng lớp của bạn có thể sử dụng using(hoặc, một lần nữa, đi cho Mẫu Loại bỏ).
Dorus

13

Các tài liệu nói rằng hai phương pháp này là tương đương:

StreamReader.C Đóng: Việc triển khai Đóng này gọi phương thức Vứt bỏ một giá trị thực.

StreamWriter.C Đóng: Việc triển khai Đóng này gọi phương thức Vứt bỏ một giá trị thực.

Stream.C Đóng : Phương thức này gọi Dispose, chỉ định true để giải phóng tất cả các tài nguyên.

Vì vậy, cả hai đều có giá trị như nhau:

/* Option 1, implicitly calling Dispose */
using (StreamWriter writer = new StreamWriter(filename)) { 
   // do something
} 

/* Option 2, explicitly calling Close */
StreamWriter writer = new StreamWriter(filename)
try {
    // do something
}
finally {
    writer.Close();
}

Cá nhân, tôi sẽ gắn bó với tùy chọn đầu tiên, vì nó chứa ít "tiếng ồn" hơn.


5

Trên nhiều lớp hỗ trợ cả hai Close()Dispose()phương thức, hai cuộc gọi sẽ tương đương nhau. Tuy nhiên, trên một số lớp, có thể mở lại một đối tượng đã bị đóng. Một số lớp như vậy có thể giữ một số tài nguyên còn sống sau khi Đóng, để cho phép mở lại; những người khác có thể không giữ bất kỳ tài nguyên nào còn tồn tại Close(), nhưng có thể đặt cờ Dispose()để cấm mở lại một cách rõ ràng.

Hợp đồng IDisposable.Disposerõ ràng yêu cầu rằng việc gọi nó trên một đối tượng sẽ không bao giờ được sử dụng lại sẽ trở nên vô hại nhất, vì vậy tôi khuyên bạn nên gọi một IDisposable.Disposehoặc một phương thức được gọi Dispose()trên mọi IDisposableđối tượng, cho dù người ta có gọi hay không Close().


FYI đây là một bài viết trên blog MSDN giải thích niềm vui Đóng và Loại bỏ. blogs.msdn.com/b/kimhamil/archive/2008/03/15/...
JamieSee

1

Đây là một câu hỏi cũ, nhưng bây giờ bạn có thể viết bằng cách sử dụng các câu lệnh mà không cần phải chặn từng câu. Chúng sẽ được xử lý theo thứ tự ngược lại khi khối chứa kết thúc.

using var responseStream = response.GetResponseStream();
using var reader = new StreamReader(responseStream);
using var writer = new StreamWriter(filename);

int chunkSize = 1024;
while (!reader.EndOfStream)
{
    char[] buffer = new char[chunkSize];
    int count = reader.Read(buffer, 0, chunkSize);
    if (count != 0)
    {
        writer.Write(buffer, 0, count);
    }
}

https://docs.microsoft.com/en-us/dotnet/csharp/lingu-reference/proposeals/csharp-8.0/use

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.