Đóng và vứt bỏ - để gọi?


Câu trả lời:


190

Tôi muốn làm rõ tình huống này.

Theo hướng dẫn của Microsoft, đây là một cách thực hành tốt để cung cấp Closephương pháp phù hợp. Dưới đây là một trích dẫn từ hướng dẫn thiết kế khung

Xem xét việc cung cấp phương pháp Close(), ngoài việc Dispose(), nếu đóng là thuật ngữ tiêu chuẩn trong khu vực. Khi làm như vậy, điều quan trọng là bạn phải Closethực hiện giống hệt Dispose...

Trong hầu hết các trường hợp CloseDisposephương pháp là tương đương. Sự khác biệt chính giữa CloseDisposetrong trường hợp SqlConnectionObjectlà:

Một ứng dụng có thể gọi Closenhiều hơn một lần. Không có ngoại lệ được tạo ra.

Nếu bạn gọi trạng thái đối tượng Disposephương thức SqlConnectionsẽ được thiết lập lại. Nếu bạn cố gắng gọi bất kỳ phương thức nào trên SqlConnection đối tượng bị loại bỏ , bạn sẽ nhận được ngoại lệ.

Mà nói:

  • Nếu bạn sử dụng đối tượng kết nối một lần, sử dụng Dispose.
  • Nếu đối tượng kết nối phải được sử dụng lại, sử dụng Closephương thức.

5
@Chris, tài liệu cho Close () cho biết "Sau đó, nó sẽ giải phóng kết nối đến nhóm kết nối hoặc đóng kết nối nếu nhóm kết nối bị vô hiệu hóa." Vì vậy, Đóng () phải đủ để giữ cho nhóm kết nối không bị tràn.
David Hammond

@DavidHammond: Bạn nói đúng. Tôi đang xóa bình luận trước đây của tôi.
NotMe

3
Liệu .Dispose () cũng giải phóng kết nối trở lại nhóm?
oscilatingcretin

Đây là lập luận tốt nhất mà tôi đã đọc về chủ đề này hay cách khác trong một thập kỷ. Điểm tuyệt vời.
Michael Erickson

1
Vì vậy, nó hoạt động theo cách này 1. con.Open() con.Close(); 2 con.Open(); // reuse 3. con.Dispose(); // use one time con.Open(); // error
shaijut

24

Như thường lệ, câu trả lời là: nó phụ thuộc. Các lớp khác nhau thực hiện IDisposabletheo những cách khác nhau và tùy thuộc vào bạn để thực hiện nghiên cứu cần thiết.

Theo như SqlClientđi, thực hành được khuyến nghị là làm như sau:

using (SqlConnection conn = /* Create new instance using your favorite method */)
{
    conn.Open();
    using (SqlCommand command = /* Create new instance using your favorite method */)
    {
        // Do work
    }
    conn.Close(); // Optional
}

Bạn nên gọi Dispose(hoặc Close*) trên kết nối! Đừng không chờ cho thu gom rác để làm sạch kết nối của bạn, điều này sẽ buộc các kết nối trong hồ bơi cho đến khi chu kỳ GC tiếp theo (ít nhất). Nếu bạn gọi Dispose, không cần thiết phải gọi Close, và vì usingcấu trúc giúp dễ dàng xử lý Disposechính xác, nên thực sự không có lý do gì để gọi Close.

Các kết nối được tự động gộp lại và gọi Dispose/ Closetrên kết nối không đóng kết nối vật lý (trong trường hợp bình thường). Đừng cố gắng thực hiện tổng hợp của riêng bạn. SqlClientthực hiện dọn dẹp kết nối khi nó được truy xuất từ ​​nhóm (như khôi phục bối cảnh cơ sở dữ liệu và các tùy chọn kết nối).

* nếu bạn đang gọi Close, hãy đảm bảo thực hiện theo cách an toàn ngoại lệ (nghĩa là trong một lần bắt hoặc cuối cùng là chặn).


Khi bạn nói, "tùy thuộc vào bạn để thực hiện nghiên cứu cần thiết", nghiên cứu đó là gì? Cách duy nhất tôi biết cách nói chắc chắn là thông qua Reflection nhưng điều đó có nhược điểm là "bất hợp pháp" trong hầu hết các tình huống.
Bão

6
Tôi sẽ không nói: conn.Close(); // OptionalNó không phải là tùy chọn. Nó dư thừa và không cần thiết. Bạn đang xử lý đối tượng hai lần và điều này sẽ được đánh dấu là cảnh báo bởi một số công cụ phân tích mã.
Metalogic

@Metalogic Tôi đồng ý rằng nó không cần thiết (và xấu) để gọi Đóng với cách sử dụng hợp lý. Tuy nhiên, nitpicking: gọi Đóng không phải là "xử lý" (trong khi Vứt bỏ ngụ ý Đóng cho một SqlConnection). So sánh với using (var x = ..) { x.Dispose(); }, trong trường hợp đó xthực sự là "xử lý hai lần".
dùng2864740

11

Bạn cần gọi Dispose ()!

Vứt bỏ () là để nhà phát triển gọi, Garbage Collector gọi Finalize (). Nếu bạn không gọi Dispose () trên các đối tượng của mình thì mọi tài nguyên không được quản lý mà chúng sử dụng sẽ không được xử lý cho đến khi người thu gom rác xuất hiện và gọi hoàn thiện chúng (và ai biết khi nào điều đó sẽ xảy ra).

Kịch bản này được gọi là Quyết định không xác định và là một cái bẫy phổ biến cho các nhà phát triển .net. Nếu bạn đang làm việc với các đối tượng triển khai IDis Dùng thì hãy gọi Dispose () trên chúng!

http://www.ondotnet.com/pub/a/oreilly/dotnet/news/programmingCsharp_0801.html?page=last

Mặc dù có thể có nhiều trường hợp (như trên SqlConnection) khi bạn gọi Disponse () trên một số đối tượng và nó chỉ đơn giản gọi Close () trên kết nối của nó hoặc đóng một tay cầm tệp, gần như luôn luôn là lựa chọn tốt nhất của bạn để gọi Dispose ()! trừ khi bạn có kế hoạch tái sử dụng đối tượng trong tương lai rất gần.


26
Nhận xét này là hoàn toàn sai. Người thu gom rác không bao giờ, không bao giờ gọi Dispose.
Stephen Cleary

3
Hệ quả: Bạn nên gọi Dispose() nếu bạn không sử dụng using()với một lớp thực hiện IDisposable. Nếu lớp được gọi là thực hiện IDis Dùng một lần và bạn đã kết thúc việc sử dụng nó trên trang bên trong using(), thì bạn có thể loại bỏ bằng Dispose()(ý định chơi chữ, vì vậy hãy bắn tôi). Close()Tuy nhiên, việc sử dụng được khuyến nghị với bất kỳ thứ gì sử dụng rõ ràng Open(), AFAIK.
René Kåbis

Tôi không chắc chắn về các DBMS khác, nhưng bạn KHÔNG thể làm cả hai trong PostgreSql . Khi bạn Closekết nối, Postgres sẽ tự động đặt định danh kết nối thành null. Từ đó trở đi, người ta không thể Disposenhận dạng kết nối sql đã được đặt thành null.
ssd

10

Đối với SqlConnection, từ quan điểm của chính kết nối, chúng là tương đương. Theo Reflector, Dispose()các cuộc gọiClose() cũng như thực hiện một vài thao tác giải phóng bộ nhớ bổ sung - chủ yếu bằng cách đặt các thành viên bằng null.

Đối với Stream, chúng thực sự là tương đương. Stream.Dispose()chỉ cần gọi Đóng ().


1
Bạn có chắc không? MSDN cho biết nó được thừa hưởng từComponent đó dường như không làm gì để thử và gọiClose() . Tôi không thể thấy bất cứ nơi nào trong DBConnectionhoặc SqlConnectioncó liên quan đến một trong những thông báo đó. Tuy nhiên, nó có một cái riêng DisposeMe()không được tham chiếu ở bất cứ đâu .
Deanna

@Deanna nó được ghi đè ở đây: github.com/dotnet/corefx/blob/ mẹo
David Cumps

@DavidCumps Có vẻ như nó đã thay đổi trong 4 năm kể từ khi tôi viết bình luận đó. Liên kết của tôi không còn hiệu lực.
Deanna


6

Lời khuyên nhanh chóng này sẽ trở thành một câu trả lời dài. Lấy làm tiếc.

Như ông trùm đã chỉ ra trong câu trả lời hay của mình, gọi Dispose() là một thực hành lập trình tuyệt vời. Điều này là do phương pháp này được cho là "tập hợp lại" tất cả các giải phóng tài nguyên cần thiết để không có tài nguyên mở không cần thiết. Ví dụ, nếu bạn đã viết một số văn bản vào một tệp và không đóng tệp (giải phóng tài nguyên), nó sẽ vẫn mở và không ai khác có thể ghi vào đó cho đến khi GC xuất hiện và làm những gì bạn nên có làm xong.

Bây giờ, trong một số trường hợp, sẽ có các phương thức "hoàn thiện" cụ thể hơn cho lớp bạn đang xử lý, như StreamWriter.Close(), ghi đè TextWriter.Close(). Thật vậy, chúng thường phù hợp hơn với tình huống: Close()ví dụ, một StreamWriter , tuôn ra luồng và bộ mã hóa cơ bản trước khi Dispose()ing đối tượng! Mát mẻ!

Tuy nhiên, duyệt MSDN, bạn sẽ thấy rằng ngay cả Microsoft đôi khi cũng bị nhầm lẫn bởi vô số các công cụ đóng và xử lý. Ví dụ, trong trang web này , trong một số ví dụ Close()được gọi trước ẩn Dispose()(xem sử dụng câu lệnh nếu bạn không hiểu tại sao nó ẩn) và đặc biệt là trong một ví dụ họ không bận tâm. Tại sao lại như vậy? Tôi cũng đã bối rối.

Lý do tôi tìm ra (và, tôi nhấn mạnh, đây là nghiên cứu ban đầu và tôi chắc chắn có thể mất danh tiếng nếu tôi sai) là điều đó Close()có thể thất bại, mang lại một ngoại lệ trong khi để tài nguyên mở, trong khi Dispose()chắc chắn sẽ giải phóng chúng . Đó là lý do tại sao một Dispose()luôn luôn phải bảo vệ một Close()cuộc gọi (xin lỗi vì chơi chữ).

MyResource r = new MyResource();

try {
  r.Write(new Whatever());

  r.Close()
finally {
  r.Dispose();
}

Và vâng, tôi đoán Microsoft đã trượt vào một ví dụ đó. Có lẽ dấu thời gian đó sẽ không bao giờ bị xóa vào tập tin.

Tôi đang sửa mã cũ vào ngày mai.

Chỉnh sửa: xin lỗi Brannon, tôi không thể nhận xét về câu trả lời của bạn, nhưng bạn có chắc chắn nên gọi Close()cho một finallykhối không? Tôi đoán một ngoại lệ từ đó có thể phá hỏng phần còn lại của khối, có khả năng sẽ chứa mã dọn dẹp quan trọng.

Trả lời Brannon's: tuyệt vời, chỉ cần đừng quên gọi Close()khi thực sự cần thiết (ví dụ: khi xử lý các luồng - không biết nhiều về các kết nối SQL trong .NET).


Trên thực tế, tôi không bao giờ gọi Close (), tôi chỉ để Dispose () và cấu trúc 'bằng cách sử dụng' thực hiện đúng . Nếu bạn không gọi Vứt bỏ, thì bạn cần gọi Đóng theo cách an toàn ngoại lệ. Nó có thể là một ý tưởng tốt để thêm xử lý ngoại lệ vào khối cuối cùng.
Brannon

Đúng vậy, ý kiến ​​của tôi là dành cho SqlClient. Vấn đề là, bạn cần hiểu các lớp bạn đang sử dụng. Luôn luôn gọi Vứt bỏ không nhất thiết là câu trả lời đúng.
Brannon

2

Đánh máy chữ sang iDis Dùng một lần và gọi vứt bỏ nó. Điều đó sẽ gọi bất kỳ phương thức nào được cấu hình là thực hiện "iDis Dùng một lần.", Bất kể hàm được đặt tên là gì.


"Hàm được đặt tên" 'Vứt bỏ': vì vậy chúng tôi quay lại câu hỏi ban đầu:}
user2864740

Hàm bị ràng buộc IDisposable.Dispose, nhưng điều đó không có nghĩa đó là tên. Lưu ý rằng trong vb.net, có thể có một chức năng được liên kết với nhiều thành viên giao diện với các tên không cần liên quan đến chức năng đó.
supercat

Diễn viên như thế này:using (myObj as IDisposable)
Yousha Aleayoub

2

Nói chung, chúng tôi đang phải đối mặt với vấn đề trong Đóng (), Hủy bỏ () và Loại bỏ () nhưng để tôi cho bạn biết sự khác biệt giữa chúng.

1) TRẢ LỜI: - Tôi sẽ không đề xuất sử dụng điều này bởi vì khi hủy bỏ được gọi, máy khách sẽ xóa kết nối mà không báo cho máy chủ để máy chủ sẽ đợi trong một khoảng thời gian (khoảng 1 phút). Nếu bạn có yêu cầu số lượng lớn thì bạn không thể sử dụng hủy bỏ () vì điều đó có thể khiến hết thời gian cho nhóm kết nối bị giới hạn của bạn.

2) Đóng: - Đóng là cách rất tốt để đóng kết nối vì khi đóng kết nối, nó sẽ gọi máy chủ và xác nhận máy chủ cũng đóng bên đó.

Ở đây, một điều nữa để xem xét. Trong một số trường hợp, nếu lỗi tạo ra thì đó không phải là cách tốt để viết mã cuối cùng là kết nối đó. Đóng () vì tại thời điểm đó Trạng thái giao tiếp sẽ bị lỗi.

3) Loại bỏ: - Đây là một loại đóng nhưng sau khi đóng kết nối, bạn không thể mở lại.

Vì vậy, hãy thử cách này,

private void CloseConnection(Client client)
    {
        if (client != null && client.State == CommunicationState.Opened)
        {
            client.Close();
        }
        else
        {
            client.Abort();
        }
    }

Kiểm tra trên client != nulllà không chính xác / gây hiểu lầm bởi vì nó không bảo vệ tất cả các công dụng. Ngoài ra, tôi không chắc làm thế nào mã có thể chuyển sang trạng thái "kết nối này không được mở và nên được đóng lại".
dùng2864740
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.