正确实现IDisposable


145

在我的课程中,我实现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:正确实现IDisposable

我已经通读了此页面,但是恐怕我不太了解在这里需要做什么。

如果任何人都可以用更深刻的解释来解释问题是什么和/或应该如何实现IDisposable,那将真的有帮助!


1
那是里面的所有代码Dispose吗?
克劳迪奥·雷迪

42
您应该实现Dispose()方法,以在您的任何类成员上调用Dispose()方法。这些成员没有一个。因此,您应该实现IDisposable。重置属性值是没有意义的。
汉斯·帕桑

13
你只需要实现IDispoable,如果你有非托管资源的处置(包括那些被包装(非托管资源SqlConnectionFileStream等等)。你不和不应该执行IDisposable,如果你只有管理资源,如在这里。这是,海事组织,代码分析的一个主要问题,非常擅长检查愚蠢的小规则,但擅长检查概念错误
jason

51
有些人宁愿投反对票,并且看到这个问题已经解决,而不是试图帮助一个明显误解了这个概念的人,这让我感到非常沮丧。多可惜。
Ortund

2
因此,请不要投票,不要投票,将帖子保留为零,并使用有用的指针来结束问题。
tjmoore 2014年

Answers:


113

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

  1. 您拥有不受管理的资源
  2. 您要坚持引用那些本身就是一次性的东西。

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

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
当我开始用C#编写代码时,我被告知最好using(){ }在可能的情况下使用它,但是要做到这一点,您需要实现IDisposable,因此,总的来说,我更喜欢通过usings来访问类,尤其是。如果我只需要一个或两个函数中的类
Ortund

62
@Ortund你误会了。当类实现IDisposable时,最好使用一个using块。如果您不需要一个可替代的类,请不要实施它。它没有任何目的。
丹尼尔·曼

5
@DanielMann但是,仅usingIDisposable接口之外,块的语义确实具有吸引力。我想象有很多滥用行为IDisposable只是为了确定范围。
2014年

1
就像一个旁注,如果您要释放任何非托管资源,则应包括Finalizer调用Dispose(false),这将允许GC在进行垃圾收集时调用Finalizer(以防万一尚未调用Dispose的情况),并正确释放非托管资源资源。
mariozski '16

4
在实现中没有终结器的情况下,调用GC.SuppressFinalize(this);是没有意义的。正如@mariozski指出的,如果在块内未使用该类,则终结器将有助于确保Dispose完全调用该终结器using
Haymo Kutschbach

57

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

但是,假设这只是一个学习活动,建议的实现方式IDisposable是添加一个“安全陷阱”,以确保不会两次处置任何资源:

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,有一个标志来确保清除代码仅执行一次是一种方法,比将属性设置为null或其他任何方法都更好(特别是因为这会干扰readonly语义)
Thomas

+1用于使用用户代码(即使该代码将自动清除)也可以使代码清楚。同样,因为他不是咸水手,并且像在这里学习其他人一样,在犯错误时会重罚他。
Murphybro2 '19

42

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

请记住,仅当您的类中具有非托管资源时才需要析构函数(finalizer)。而且,如果添加了析构函数,则应在Dispose中抑制Finalization,否则它将导致您的对象在内存中停留两个垃圾回收周期(注意:阅读Finalization的工作原理)。下面的例子详细说明了上面。

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

IDisposable存在的目的是为您提供一种清理非托管资源的方法,这些资源不会被垃圾收集器自动清理。

您正在“清理”的所有资源都是托管资源,因此您的Dispose方法无法完成任何工作。您的课程根本不应该实现IDisposable。垃圾收集器将自行处理所有这些字段。


1
对此表示同意-有一种想法是在实际上不需要时丢弃所有东西。仅当您有非托管资源需要清理时,才应使用处置!!
Chandramouleswaran Ravichandra14年

4
并非完全正确,Dispose方法还允许您处置“实现IDisposable的托管资源”
Matt Wilko 2015年

@MattWilko这使它成为处理非托管资源的间接方式,因为它允许另一个资源处理非托管资源。这里甚至没有通过另一个托管资源间接引用非托管资源的信息。
2015年

@MattWilko Dispose将自动调用实现IDesposable的任何托管资源
panky sharma

@pankysharma不,不会。需要手动调用它。这就是重点。您不能假设它会被自动调用,只知道人们应该手动调用它,但是人们却会犯错误而忘记了。
Servy

14

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

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
在析构函数中调用GC.SuppressFinalize(this)会更明智吗?否则,对象本身将在下一次GC中回收
Sudhanshu Mishra'3

2
@dotnetguy:gc运行时调用对象析构函数。因此,两次调用是不可能的。参见此处:msdn.microsoft.com/en-us/library/ms244737.aspx
schoetbi 2015年

1
因此,现在我们是否将任何样板代码称为“模式”?
Chel

4
@rdhs不,我们不是。MSDN表示此处 “处置模式”的模式-msdn.microsoft.com/zh-cn/library/b1yfkh5e(v=vs.110).aspx,因此在拒绝投票之前,可能要对Google稍作投票吗?
Belogix

2
微软和您的帖子都没有明确说明为什么该模式应如下所示。通常,它甚至都不是样板文件,只是多余的-被SafeHandle(和子类型)所取代。在管理资源的情况下,适当处置变得简单得多;您可以将代码缩减为该void Dispose()方法的简单实现。
BatteryBackupUnit

10

您不需要做您的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。在这种边际用例中,似乎可以忽略编译器的警告。
祖蓝精灵

3

每当需要确定性(已确认)垃圾收集时,isdisposable便是一种实现。

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方法进行处置。


2

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

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

唯一可能有用的其他事情是抑制该代码分析警告... https://docs.microsoft.com/zh-cn/visualstudio/code-quality/in-source-suppression-overview?view=vs- 2017年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.