代码之家  ›  专栏  ›  技术社区  ›  mafu

我实现IDisposable是否正确?

  •  28
  • mafu  · 技术社区  · 15 年前

    此类使用 StreamWriter 因此实施 IDisposable .

    public class Foo : IDisposable
    {
        private StreamWriter _Writer;
    
        public Foo (String path)
        {
            // here happens something along the lines of:
            FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
            _Writer = new StreamWriter (fileWrite, new ASCIIEncoding ());
        }
    
        public void Dispose ()
        {
            Dispose (true);
            GC.SuppressFinalize (this);
        }
    
        ~Foo()
        {
            Dispose (false);
        }
    
        protected virtual void Dispose (bool disposing)
        {
            if (_Disposed) {
                return;
            }
            if (disposing) {
                _Writer.Dispose ();
            }
            _Writer = null;
            _Disposed = true;
        }
        private bool _Disposed;
    }
    

    }

    当前的实施有什么问题吗?也就是说,我是否必须释放底层 FileStream 手动?是 Dispose(bool) 写得正确吗?

    7 回复  |  直到 11 年前
        1
  •  39
  •   AnthonyWJones    15 年前

    如果类不直接使用非托管资源,则不需要使用IDisposable实现的扩展版本。

    简单的

     public virtual void Dispose()
     {
    
         _Writer.Dispose();
     }
    

    就够了。

    如果您的消费者未能处置您的对象,则它将在没有调用Dispose的情况下正常进行gc'd处理,而由编写器持有的对象也将进行gc'd处理,并且它将有一个最终确定者,因此它仍然可以正确地清理其非托管资源。

    编辑

    在对Matt和其他人提供的链接做了一些研究之后,我得出结论,我的答案在这里 林分 . 以下是为什么:

    可继承类上可释放实现“模式”(我的意思是受保护的虚拟释放(bool)、SuppressFinalize等)背后的前提是子类 可以 保留非托管资源。

    然而,在现实世界中,我们绝大多数.NET开发人员从未接近过非托管资源。如果你必须量化 可以 在上面,你会想出什么样的.NET编码的可能性数字?

    假设我有一个person类型(为了讨论,它在某个字段中有一个可弃类型,因此应该是可弃类型本身)。现在我有了继承者、客户、员工等。我是否真的有理由用这种“模式”来混乱Person类,以防有人继承了Person并希望持有非托管资源?

    有时,我们的开发人员在尝试为所有可能的情况编写代码时,可能会过度复杂化事情,而不使用关于这种情况相对概率的一些常识。

    如果我们曾经想直接使用非托管资源,那么明智的模式将把这样一个东西包装在自己的类中,在这个类中,完整的“可释放模式”是合理的。因此,在大量的“正常”代码中,我们不必担心所有这些乱七八糟的事情。如果我们需要IDisposable,我们可以使用上面的简单模式,是否可继承。

    啊,很高兴把它从我的胸口拿开。;)

        2
  •  16
  •   Matt Howells    15 年前

    你不需要 Finalize (析构函数)方法,因为您没有任何非托管对象。但是,您应该保持呼叫 GC.SuppressFinalize 如果从foo继承的类具有非托管对象,因此具有终结器。

    如果将类设置为密封的,那么就知道非托管对象永远不会进入公式,因此不必添加 protected virtual Dispose(bool) 过载,或 抑制终结 .

    编辑:

    @AnthonyWjones对此的反对意见是,如果您知道子类不会引用非托管对象,那么 Dispose(bool) 抑制终结 是不必要的。但如果是这样的话,你真的应该去上课 internal 而不是 public Dispose() 方法应该是 virtual . 如果你知道你在做什么,那么不要遵循微软的建议模式,但是你应该在打破规则之前知道并理解它们!

        3
  •  4
  •   Mike J    15 年前

    建议的做法是仅在具有非托管资源(如本机文件句柄、内存指针等)时使用终结器。

    不过,我有两个小建议,

    没有必要 “ m_Disposed “要测试的变量 你以前打过电话 Dispose 管理资源。你可以 使用类似的代码,

    protected virtual void Dispose (bool disposing)
    {
        if (disposing) {
            if (_Writer != null)
                _Writer.Dispose ();
        }
        _Writer = null;
    }
    

    仅在必要时打开文件。因此,在您的示例中,您将使用 File.Exists 然后当你需要读/写文件时, 然后 你会打开并使用它。

    另外,如果你只是想把文本写进一个文件,那就看看 File.WriteAllText File.OpenText 甚至 File.AppendText 专门针对文本文件 ASCIIEncoding .

    除此之外,是的,您正在实现.NET 处置 正确的模式。

        4
  •  3
  •   Eamon Nerbonne    15 年前

    我有很多这样的课程,我的建议是尽可能的把课程封起来。 IDisposable +继承可以工作,但99%的时候你不需要它——而且很容易出错。

    除非您正在编写一个公共API(在这种情况下,允许人们实现您的代码是很好的,不管他们希望如何——也就是使用 不可分的 )你不需要支持它。

    只做:

    public sealed class Foo : IDisposable
    {
        private StreamWriter _Writer;
    
        public Foo(string path)
        {
                FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
                try { 
                    _Writer = new StreamWriter (fileWrite, new ASCIIEncoding());
                } catch {
                    fileWrite.Dispose();
                    throw;
                }
        }
    
        public void Dispose()
        {
             _Writer.Dispose();
        }
    }
    

    注意尝试…抓住;技术上 StreamWriter 构造函数可能引发异常,在这种情况下,它永远不会拥有 FileStream 你必须自己处理。

    如果你真的用了很多 不可分的 S,考虑将代码放入C++/CLI中:它为您提供所有样板代码(它透明地使用适当的确定性破坏技术用于本地和托管对象)。

    维基百科有一个象样的ID可供C++的样本(真的,如果你有很多IDISPOSILD的,C++是实际的。 许多的 比c)简单: Wikipedia: C++/CLI Finalizers and automatic variables .

    例如,在C++/CLI中实现一个“安全”的一次性容器看起来像…

    public ref class MyDisposableContainer
    {
        auto_handle<IDisposable> kidObj;
        auto_handle<IDisposable> kidObj2;
    public:
    
        MyDisposableContainer(IDisposable^ a,IDisposable^ b) 
                : kidObj(a), kidObj2(b)
        {
            Console::WriteLine("look ma, no destructor!");
        }
    };
    

    即使不添加自定义的IDisposable实现,此代码也将正确地处理kidobj和kidobj2,并且它对于任意一个Dispose实现中的异常都是健壮的(不应该发生这些异常,但仍然如此),并且在面对更多的异常时维护起来很简单 不可分的 成员或本机资源。

    不是我是一个巨大的C++/CLI的粉丝,但是对于重资源导向的代码,它很容易被C击败,并且它与托管代码和本机代码有着绝对的完美互操作,简而言之,完美的胶粘代码;我倾向于在C 90%中编写我的代码,但是对于所有的互操作需求(尤其是如果你想调用任何Win32函数——MalSalas和其他互操作属性,都是恐怖的,完全不可理解的),使用C++/CLI。

        5
  •  1
  •   LukeH    15 年前

    你应该检查一下 _Writer 不是 null 在试图处理它之前。(看起来不太可能 无效的 但以防万一!)

    protected virtual void Dispose(bool disposing)
    {
        if (!_Disposed)
        {
            if (disposing && (_Writer != null))
            {
                _Writer.Dispose();
            }
            _Writer = null;
            _Disposed = true;
        }
    }
    
        6
  •  0
  •   Tal Pressman    15 年前

    如果打开streamwriter,也必须将其处理掉,否则会发生泄漏。

        7
  •  0
  •   Yort    15 年前

    我同意其他评论中所说的一切,但也会指出这一点;

    1. 在这两种情况下,实际上都不需要设置_writer=null。

    2. 如果你打算这样做,最好是把它放在如果处置的地方。这可能没有什么大的区别,但一般来说,当你被决赛选手处置时,你不应该玩弄被管理的对象(正如其他人指出的那样,在这种情况下你不需要)。