正确实施 IDisposable

Posted

技术标签:

【中文标题】正确实施 IDisposable【英文标题】:Implementing IDisposable correctly 【发布时间】:2013-08-22 14:39:33 【问题描述】:

在我的课程中,我实现IDisposable 如下:

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;
    

在 VS2012 中,我的代码分析说要正确实现 IDisposable,但我不确定我在这里做错了什么。 具体文字如下:

CA1063 正确实现 IDisposable 在“用户”上提供可覆盖的 Dispose(bool) 实现或将类型标记为密封。对 Dispose(false) 的调用应该只清理本机资源。对 Dispose(true) 的调用应该清理托管资源和本机资源。 stman User.cs 10

供参考:CA1063: Implement IDisposable correctly

我已经阅读了这个页面,但恐怕我不太明白这里需要做什么。

如果有人能用更通俗的语言解释问题是什么和/或应该如何实现IDisposable,那真的很有帮助!

【问题讨论】:

这就是Dispose里面的所有代码吗? 你应该实现你的 Dispose() 方法来调用你类的任何成员的 Dispose() 方法。这些成员都没有。因此,您应该实现 IDisposable。重置属性值是没有意义的。 你只需要实现IDispoable,如果你有非托管资源要处理(这包括被包装的非托管资源(SqlConnectionFileStream等)。你不需要并且不应该如果你只有像这里这样的托管资源,就不应该实现IDisposable。这是,IMO,代码分析的一个主要问题。它非常擅长检查愚蠢的小规则,但不是 i> 善于检查概念错误。 @Ortund 已经有大量关于一次性模式的资料。即使在这个问题的答案中,也有一些误解模式的微妙例子。最好将未来的提问者指向第一个相关的 SO 问题(有 309 个赞成票)。 所以不要投反对票,也不要投赞成票,将帖子保持为零并用有用的指针结束问题。 【参考方案1】:

这将是正确的实现,尽管我在您发布的代码中看不到您需要处理的任何内容。您只需要在以下情况下实现IDisposable

    您有非托管资源 您坚持对本身是一次性的事物的引用。

您发布的代码中的任何内容都不需要处理。

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.
    

【讨论】:

当我开始用 C# 编写代码时,我被告知最好尽可能使用using() ,但要做到这一点,您需要实现 IDisposable,所以总的来说,我更喜欢访问通过 usings 上课,尤其是。如果我只需要一个或两个函数中的类 @Ortund 你误会了。 当类实现 IDisposable 时,最好使用using 块。如果您不需要一个类是一次性的,请不要实现它。它没有任何用处。 @DanielMann 不过,using 块的语义确实比IDisposable 接口本身更有吸引力。我想IDisposable 的滥用不止一些,只是为了确定范围。 就像旁注一样,如果您要释放任何非托管资源,您应该包括调用 Dispose(false) 的 Finalizer,这将允许 GC 在进行垃圾收集时调用 Finalizer(如果 Dispose 是尚未调用)并正确释放非托管资源。 在你的实现中没有终结器调用GC.SuppressFinalize(this); 是没有意义的。正如@mariozski 指出的那样,如果在using 块中未使用该类,则终结器将有助于确保完全调用Dispose【参考方案2】:

首先,您不需要“清理”strings 和 ints - 它们将由垃圾收集器自动处理。在Dispose 中唯一需要清理的是非托管资源或实现IDisposable 的托管资源。

但是,假设这只是一个学习练习,实现IDisposable推荐方法是添加一个“安全捕获”以确保任何资源都不会被两次处理: p>

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;   
    

【讨论】:

+1,有一个标志来确保清理代码只执行一次,这比将属性设置为 null 或其他任何东西要好得多(特别是因为这会干扰readonly 语义) +1 用于使用用户代码(即使它会被自动清理)以明确那里的内容。另外,因为他不是一个咸水手,并且在像这里的许多其他人一样学习的同时,因为犯了一个小错误而责备他。 IDisposable 的约定是多次调用Dispose 是无害的(它不能破坏对象或抛出异常)。记住对象已被释放主要是为了使无法在已释放对象上使用的方法可以提前抛出ObjectDisposedException(而不是在对包含的对象调用方法时,甚至导致意外的不同异常)。【参考方案3】:

以下示例显示了实现IDisposable 接口的一般最佳实践。 Reference

请记住,只有当您的类中有非托管资源时,您才需要析构函数(终结器)。如果你添加了析构函数,你应该在 Dispose 中抑制 Finalization,否则会导致你的对象在内存中驻留两个垃圾周期(注意:Read how Finalization works)。下面的例子详细说明了以上所有内容。

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.
    

【讨论】:

【参考方案4】:

IDisposable 的存在为您提供了一种方法来清理垃圾收集器不会自动清理的非托管资源。

您正在“清理”的所有资源都是托管资源,因此您的 Dispose 方法什么也没做。你的班级根本不应该实现IDisposable。垃圾收集器会自行处理所有这些字段。

【讨论】:

同意这一点 - 有一个概念是在实际上不需要时处理所有内容。仅当您有非托管资源需要清理时才应使用 Dispose! 严格来说不是这样,Dispose 方法还允许您处理“实现 IDisposable 的托管资源” @MattWilko 这使它成为一种处置非托管资源的间接方式,因为它允许另一个资源处置非托管资源。这里甚至没有通过另一个托管资源对非托管资源的间接引用。 @MattWilko Dispose 将自动调用任何实现 IDesposable 的托管资源 @pankysharma 不,不会。它需要手动调用。这就是它的全部意义所在。你不能假设它会被自动调用,你只知道人们应该手动调用它,但人们确实会犯错并忘记。【参考方案5】:

您需要像这样使用一次性模式

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);

【讨论】:

在析构函数中调用 GC.SuppressFinalize(this) 不是更明智吗?否则对象本身会在下一次 GC 中被回收 @dotnetguy:对象析构函数在 gc 运行时被调用。所以调用两次是不可能的。见这里:msdn.microsoft.com/en-us/library/ms244737.aspx 那么现在我们是否将任何样板代码称为“模式”? @rdhs 不,我们不是。 MSDN 在这里声明它IS 是一个“Dispose Pattern”模式 - msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx 所以在否决之前也许谷歌有点? Microsoft 和您的帖子都没有明确说明为什么模式应该是这样的。一般来说,它甚至不是样板文件,它只是多余的——被SafeHandle(和子类型)取代。在托管资源的情况下,实施适当的处置变得更加简单;您可以将代码精简为 void Dispose() 方法的简单实现。【参考方案6】:

您无需将User 类设置为IDisposable,因为该类不获取任何非托管资源(文件、数据库连接等)。通常,我们将类标记为 IDisposable 如果他们至少有一个 IDisposable 字段或/和属性。 在实现IDisposable时,最好按照微软的典型方案:

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);
   

【讨论】:

通常就是这样。但另一方面, using 构造打开了编写类似于 C++ 智能指针的东西的可能性,即无论 using 块如何退出都将恢复先前状态的对象。我发现这样做的唯一方法是让这样的对象实现 IDisposable。在这种边缘用例中,我似乎可以忽略编译器的警告。【参考方案7】:

Idisposable 在您需要确定性(已确认)垃圾回收时实施。

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
        
    

在创建和使用Users类时使用“using”块来避免显式调用dispose方法:

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

using 块创建的 Users 对象的末尾将通过 dispose 方法的隐式调用进行处理。

【讨论】:

【参考方案8】:

我看到了很多 Microsoft Dispose 模式的示例,这确实是一种反模式。正如许多人指出的那样,问题中的代码根本不需要 IDisposable。但是,如果您要在哪里实现它,请不要使用 Microsoft 模式。更好的答案是遵循本文中的建议:

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

唯一可能有用的另一件事是抑制代码分析警告...https://docs.microsoft.com/en-us/visualstudio/code-quality/in-source-suppression-overview?view=vs-2017

【讨论】:

以上是关于正确实施 IDisposable的主要内容,如果未能解决你的问题,请参考以下文章

我应该何时实施 IDisposable? [复制]

SbDrmServerCertificateUpdatedFunc 是不是正确实施?

正确使用“实施”

如何正确实施“photo.service”?

我是不是正确实施了我的 DAO?

正确实施 GetHashCode [重复]