Triển khai IDis Dùng chính xác


145

Trong các lớp học của tôi, tôi triển khai IDis Dùng như sau:

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int UserID)
    {
        id = UserID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

    public void Dispose()
    {
        // Clear all property values that maybe have been set
        // when the class was instantiated
        id = 0;
        name = String.Empty;
        pass = String.Empty;
    }
}

Trong VS2012, Phân tích mã của tôi nói rằng triển khai IDis Dùng chính xác, nhưng tôi không chắc mình đã làm gì sai ở đây.
Văn bản chính xác như sau:

CA1063 Triển khai IDis Dùng chính xác Cung cấp một triển khai Vượt qua (bool) có thể vượt quá trên 'Người dùng' hoặc đánh dấu loại là được niêm phong. Một cuộc gọi đến Vứt bỏ (sai) chỉ nên dọn sạch các tài nguyên bản địa. Một cuộc gọi đến Vứt bỏ (đúng) sẽ dọn sạch cả tài nguyên gốc và được quản lý. người dùng st.cs 10

Để tham khảo: CA1063: Triển khai IDis Dùng chính xác

Tôi đã đọc qua trang này, nhưng tôi sợ tôi không thực sự hiểu những gì cần phải làm ở đây.

Nếu bất cứ ai có thể giải thích bằng nhiều thuật ngữ hơn về vấn đề là gì và / hoặc cách IDis Dùng nên được thực hiện, điều đó sẽ thực sự có ích!


1
Có phải đó là tất cả các mã bên trong Dispose?
Claudio Redi

42
Bạn nên triển khai phương thức Dispose () để gọi phương thức Dispose () trên bất kỳ thành viên nào trong lớp. Không ai trong số những thành viên đó có một. Do đó, bạn không nên thực hiện IDis Dùng. Đặt lại các giá trị tài sản là vô nghĩa.
Hans Passant

13
Bạn chỉ cần thực hiện IDispoablenếu bạn có các nguồn lực không được quản lý để xử lý (điều này bao gồm các nguồn lực không được quản lý được bao bọc ( SqlConnection, FileStream, vv). Bạn không và không nên thực hiện IDisposablenếu bạn chỉ có quản lý tài nguyên như ở đây. Đây là, IMO, một vấn đề lớn với phân tích mã. Rất tốt trong việc kiểm tra các quy tắc nhỏ ngớ ngẩn, nhưng không tốt trong việc kiểm tra các lỗi khái niệm.
jason

51
Điều này khá khó chịu với tôi rằng một số người thà hạ thấp và thấy câu hỏi này khép lại hơn là cố gắng giúp một người rõ ràng đã hiểu sai một khái niệm. Xấu hổ làm sao.
Ortund

2
Vì vậy, đừng downvote, đừng upvote, hãy để bài viết ở mức 0 và đóng câu hỏi bằng một con trỏ hữu ích.
tjmoore

Câu trả lời:


113

Đây sẽ là cách thực hiện chính xác, mặc dù tôi không thấy bất cứ điều gì bạn cần loại bỏ trong mã bạn đã đăng. Bạn chỉ cần thực hiện IDisposablekhi:

  1. Bạn có tài nguyên không được quản lý
  2. Bạn đang giữ các tài liệu tham khảo về những thứ mà bản thân chỉ dùng một lần.

Không có gì trong mã bạn đăng cần phải được xử lý.

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int userID)
    {
        id = userID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

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

    protected virtual void Dispose(bool disposing)
    {
        if (disposing) 
        {
            // free managed resources
        }
        // free native resources if there are any.
    }
}

2
Tôi được thông báo khi tôi bắt đầu viết bằng C # rằng tốt nhất nên sử dụng using(){ }bất cứ khi nào có thể, nhưng để làm được điều đó, bạn cần triển khai IDis Dùng một lần, vì vậy, nói chung, tôi thích truy cập một lớp thông qua các ứng dụng, đặc biệt. nếu tôi chỉ cần lớp trong một hoặc hai chức năng
Ortund

62
@Ortund Bạn hiểu lầm. Tốt nhất là sử dụng một usingkhối khi lớp thực hiện IDis Dùng một lần . Nếu bạn không cần một lớp học dùng một lần, đừng thực hiện nó. Nó không phục vụ mục đích.
Daniel Mann

5
@DanielMann Mặc dù vậy, ngữ nghĩa của một usingkhối có xu hướng hấp dẫn ngoài IDisposablegiao diện. Tôi tưởng tượng đã có nhiều hơn một vài sự lạm dụng IDisposablechỉ nhằm mục đích phạm vi.
Thomas

1
Cũng như một lưu ý phụ nếu bạn có bất kỳ tài nguyên không được quản lý nào được giải phóng, bạn nên bao gồm gọi Finalizer là Dispose (false), điều đó sẽ cho phép GC gọi Finalizer khi thực hiện thu gom rác (trong trường hợp Dispose chưa được gọi) tài nguyên.
mariozski

4
Nếu không có một bộ hoàn thiện trong cuộc gọi thực hiện của bạn GC.SuppressFinalize(this);là vô nghĩa. Như @mariozski đã chỉ ra một bộ hoàn thiện sẽ giúp đảm bảo rằng nó Disposeđược gọi hoàn toàn nếu lớp không được sử dụng trong một usingkhối.
Haymo Kutschbach

57

Trước hết, bạn không cần phải "dọn dẹp" stringintchúng sẽ được người thu gom rác tự động chăm sóc. Điều duy nhất cần được làm sạch Disposelà các tài nguyên không được quản lý hoặc các nguồn được quản lý thực hiện IDisposable.

Tuy nhiên, giả sử đây chỉ là một bài tập học tập, cách thực hiện được đề xuấtIDisposable là thêm "bắt an toàn" để đảm bảo rằng bất kỳ tài nguyên nào không được xử lý hai lần:

public void Dispose()
{
    Dispose(true);

    // Use SupressFinalize in case a subclass 
    // of this type implements a finalizer.
    GC.SuppressFinalize(this);   
}
protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing) 
        {
            // Clear all property values that maybe have been set
            // when the class was instantiated
            id = 0;
            name = String.Empty;
            pass = String.Empty;
        }

        // Indicate that the instance has been disposed.
        _disposed = true;   
    }
}

3
+1, có một cờ để đảm bảo mã dọn dẹp chỉ được thực thi một lần là cách, cách tốt hơn là đặt thuộc tính thành null hoặc bất cứ điều gì (đặc biệt là vì điều đó can thiệp vào readonlyngữ nghĩa)
Thomas

+1 để sử dụng mã người dùng (mặc dù nó sẽ được xóa tự động) để làm cho nó rõ ràng những gì ở đó. Ngoài ra, vì không phải là một thủy thủ mặn mà và rèn giũa anh ta vì đã phạm một lỗi nhỏ trong khi học như nhiều người khác ở đây.
Murphybro2

42

Ví dụ sau đây cho thấy thực tiễn tốt nhất chung để thực hiện IDisposablegiao diện. Tài liệu tham khảo

Hãy nhớ rằng bạn cần một hàm hủy (bộ hoàn thiện) chỉ khi bạn có tài nguyên không được quản lý trong lớp. Và nếu bạn thêm một hàm hủy, bạn nên triệt tiêu Finalization trong Dispose , nếu không nó sẽ khiến các đối tượng của bạn nằm trong bộ nhớ trong hai chu kỳ rác (Lưu ý: Đọc cách Finalization hoạt động ). Dưới đây ví dụ chi tiết tất cả ở trên.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

14

IDisposabletồn tại để cung cấp một phương tiện để bạn dọn sạch các tài nguyên không được quản lý mà Bộ thu gom rác sẽ không được dọn sạch tự động.

Tất cả các tài nguyên mà bạn đang "dọn dẹp" đều là các tài nguyên được quản lý và do đó Disposephương pháp của bạn không thực hiện được gì. Lớp học của bạn không nên thực hiện IDisposableở tất cả. Garbage Collector sẽ tự mình xử lý tất cả các lĩnh vực đó.


1
Đồng ý với điều này - có một khái niệm về việc xử lý mọi thứ khi nó thực sự không cần thiết. Vứt bỏ chỉ nên được sử dụng nếu bạn có tài nguyên không được quản lý để dọn dẹp !!
Chandramouleswaran Ravichandra

4
Không hoàn toàn đúng, phương pháp Vứt bỏ cũng cho phép bạn loại bỏ "tài nguyên được quản lý triển khai IDis Dùng"
Matt Wilko

@MattWilko Điều đó làm cho nó trở thành một cách gián tiếp để xử lý các tài nguyên không được quản lý, bởi vì nó cho phép một tài nguyên khác xử lý tài nguyên không được quản lý. Ở đây thậm chí không có một tài liệu tham khảo gián tiếp đến một tài nguyên không được quản lý thông qua một tài nguyên được quản lý khác.
Phục vụ

@MattWilko Vứt bỏ sẽ tự động gọi vào bất kỳ tài nguyên được quản lý nào đã triển khai IDes Dùng
sharma

@pankysharma Không, nó sẽ không. Nó cần phải được gọi bằng tay . Đó là toàn bộ quan điểm của nó. Bạn không thể cho rằng nó sẽ tự động được gọi, bạn chỉ biết mọi người có nghĩa vụ gọi nó bằng tay, nhưng mọi người lại mắc lỗi và quên đi.
Phục vụ

14

Bạn cần sử dụng Mẫu dùng một lần như thế này:

private bool _disposed = false;

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // Dispose any managed objects
            // ...
        }

        // Now disposed of any unmanaged objects
        // ...

        _disposed = true;
    }
}

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

// Destructor
~YourClassName()
{
    Dispose(false);
}

1
Sẽ không khôn ngoan hơn khi có một cuộc gọi đến GC.SuppressFinalize (cái này) trong bộ hủy? Nếu không, chính đối tượng sẽ được thu hồi trong GC tiếp theo
Sudhanshu Mishra

2
@dotnetguy: Hàm hủy đối tượng được gọi khi gc chạy. Vì vậy, gọi hai lần là không thể. Xem tại đây: msdn.microsoft.com/en-us/l
Library / ms244737.aspx

1
Vì vậy, bây giờ chúng ta gọi bất kỳ đoạn mã soạn sẵn nào là "mẫu"?
Chel

4
@rdhs Không, chúng tôi không có. MSDN tuyên bố đây một mẫu "Loại bỏ mô hình" ở đây - msdn.microsoft.com/en-us/l Library / b1yfkh5e (v = vs.110) .aspx vì vậy trước khi bỏ phiếu có thể Google một chút?
Belogix

2
Cả Microsoft và bài đăng của bạn đều không nêu rõ lý do tại sao mô hình sẽ trông như thế này. Nói chung, nó thậm chí không phải là nồi hơi, nó chỉ là thừa - được thay thế bởi SafeHandle(và các loại phụ). Trong trường hợp các nguồn lực được quản lý thực hiện xử lý thích hợp trở nên đơn giản hơn nhiều; bạn có thể cắt mã xuống để thực hiện void Dispose()phương thức đơn giản .
BatteryBackupUnit

10

Bạn không cần phải thực hiện Userlớp của mình IDisposablevì lớp không thu được bất kỳ tài nguyên không được quản lý nào (tệp, kết nối cơ sở dữ liệu, v.v.). Thông thường, chúng tôi đánh dấu các lớp như IDisposablethể chúng có ít nhất một IDisposabletrường hoặc / và thuộc tính. Khi thực hiện IDisposable, tốt hơn nên đặt nó theo sơ đồ điển hình của Microsoft:

public class User: IDisposable {
  ...
  protected virtual void Dispose(Boolean disposing) {
    if (disposing) {
      // There's no need to set zero empty values to fields 
      // id = 0;
      // name = String.Empty;
      // pass = String.Empty;

      //TODO: free your true resources here (usually IDisposable fields)
    }
  }

  public void Dispose() {
    Dispose(true);

    GC.SuppressFinalize(this);
  } 
}

Đó thường là trường hợp. Nhưng mặt khác, cấu trúc sử dụng mở ra khả năng viết một cái gì đó giống với con trỏ thông minh C ++, cụ thể là một đối tượng sẽ khôi phục trạng thái trước đó cho dù khối sử dụng được thoát ra như thế nào. Cách duy nhất tôi tìm thấy để làm điều này là làm cho một đối tượng như vậy triển khai IDis Dùng một lần. Dường như tôi có thể bỏ qua cảnh báo của nhà soạn nhạc trong trường hợp sử dụng cận biên như vậy.
Papa Smurf

3

Idis Dùng được thực hiện bất cứ khi nào bạn muốn một bộ sưu tập rác xác định (xác nhận).

class Users : IDisposable
    {
        ~Users()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
            // This method will remove current object from garbage collector's queue 
            // and stop calling finilize method twice 
        }    

        public void Dispose(bool disposer)
        {
            if (disposer)
            {
                // dispose the managed objects
            }
            // dispose the unmanaged objects
        }
    }

Khi tạo và sử dụng lớp Người dùng, hãy sử dụng khối "sử dụng" để tránh gọi phương thức xử lý rõ ràng:

using (Users _user = new Users())
            {
                // do user related work
            }

kết thúc khối sử dụng đã tạo Đối tượng người dùng sẽ được xử lý bằng cách gọi ẩn phương thức xử lý.


2

Tôi thấy rất nhiều ví dụ về mẫu Microsoft Dispose thực sự là một mẫu chống. Như nhiều người đã chỉ ra mã trong câu hỏi không yêu cầu IDis Dùng một chút nào. Nhưng nếu bạn sẽ triển khai nó ở đâu, vui lòng không sử dụng mẫu Microsoft. Câu trả lời tốt hơn sẽ được làm theo các gợi ý trong bài viết này:

https://www.codeproject.com/Articles/29534/IDisay-What-Your-Mother-Never-Told-You- About

Điều khác duy nhất có thể hữu ích là ngăn chặn cảnh báo phân tích mã đó ... https://docs.microsoft.com/en-us/visualstudio/code-quality/in-source-suppression-overview?view=vs- 2017

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.