我正确实施IDisposable吗?

时间:2009-07-16 08:33:50

标签: c# idisposable

此类使用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;
}

}

目前的实施有什么问题吗?即,我是否必须手动释放基础FileStreamDispose(bool)写得正确吗?

7 个答案:

答案 0 :(得分:39)

如果您的类不直接使用非托管资源,则无需使用此广泛版本的IDisposable实现。

一个简单的

 public virtual void Dispose()
 {

     _Writer.Dispose();
 }

就足够了。

如果您的消费者无法处理您的对象,它将正常进行GC,而无需调用Dispose,_Writer持有的对象也将是GC并且它将有一个终结器,所以它仍然可以清理它的非托管资源得当。

修改

在对Matt和其他人提供的链接进行了一些研究后,我得出结论,我在这里的答案代表。原因如下: -

可继承类的一次性实现“模式”(我的意思是受保护的虚拟Dispose(bool),SuppressFinalize等marlarky)背后的前提是子类可能保持到一个非托管资源。

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

让我们假设我有一个Person类型(为了论证,在其中一个字段中有一个一次性类型,因此应该是一次性的)。现在我有继承者Customer,Employee等。如果有人继承了Person并希望拥有一个非托管资源,那么使用这个“模式”来混淆Person类是否合理?

有时候,我们开发人员可能会过度复杂化,试图对所有可能的情况进行编码,而不会对这种情况的相对概率使用一些常识。

如果我们想要直接使用非托管资源,那么合理的模式就是将这样的东西包装在自己的类中,其中完整的“一次性模式”是合理的。因此,在大量的“正常”代码中,我们不必担心所有这些问题。如果我们需要IDisposable,我们可以使用上面的简单模式,是否可继承。

p,很高兴从胸前把它弄下来。 ;)

答案 1 :(得分:16)

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

如果你使类密封,那么你知道非托管对象永远不会输入等式,因此没有必要添加protected virtual Dispose(bool)重载或GC.SuppressFinalize

编辑:

@ AnthonyWJones对此的反对意见是,如果您知道子类不会引用非托管对象,则不需要整个Dispose(bool)GC.SuppressFinalize。但如果是这种情况,您应该真正创建类internal而不是publicDispose()方法应该是virtual。如果您知道自己在做什么,那么请不要遵循Microsoft的建议模式,但在破坏规则之前,您应该了解并理解规则!

答案 2 :(得分:4)

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

我有两个小建议,

没有必要     “m_Disposed”变量用于测试是否     您之前曾致电Dispose     在您的托管资源上。你可以     使用类似的代码,

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

只在必要时打开文件。因此,在您的示例中,您将使用File.Exists在构造函数中测试文件是否存在,然后当您需要读取/写入文件时,然后您将打开它并使用它

此外,如果您只是想将文字写入文件,请查看File.WriteAllTextFile.OpenText甚至File.AppendText,其中的目标是专门针对{{1}的文本文件}。

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

答案 3 :(得分:3)

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

除非你正在编写一个公共API(在这种情况下允许人们实现你的代码,但他们希望 - 即使用IDisposable),你不需要支持它。

只是这样做:

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

注意尝试... catch;从技术上讲,StreamWriter构造函数可以抛出一个异常,在这种情况下,它永远不会拥有FileStream的所有权,你必须自己处理它。

如果您真的使用了很多IDisposable,请考虑将该代码放在C ++ / CLI中:它为您制作所有样板代码(它透明地为本机和托管对象使用适当的确定性破坏技术)

维基百科有一个适合C ++的IDisposable样本(实际上,如果你有很多IDisposable,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实现中的异常(不是那些应该发生但仍然存在)是健壮的,并且面对更多{而且很容易维护{1}}成员或本地资源。

并不是说我是C ++ / CLI的忠实粉丝,但是对于面向资源的大量代码来说,它很容易被C#击败,并且它与托管代码和本机代码完全互操作 - 简而言之,完美的胶水代码; - )。我倾向于在C#中编写90%的代码,但是使用C ++ / CLI来满足所有的互操作需求(特别是如果你想调用任何win32函数 - MarshalAs和其他所有互操作属性都是可怕的并且完全不可理解)。 / p>

答案 4 :(得分:1)

在尝试处置之前,您应该检查_Writer是否不是null。 (它似乎不可能是null,但为了以防万一!)

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

答案 5 :(得分:0)

如果你打开StreamWriter,你也必须处理它,否则你就会泄漏。

答案 6 :(得分:0)

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

  1. 在任何一种情况下,您实际上都不需要设置_Writer = null。

  2. 如果你打算这样做,最好把它放在如果处置的地方。它可能不会产生很大的差异,但是你通常不应该在被终结者处理时使用托管对象(在这种情况下你不需要,正如其他人所指出的那样)。