29

此类使用 aStreamWriter并因此实现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)写对了吗?

4

7 回答 7

39

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

一个简单的

 public virtual void Dispose()
 {

     _Writer.Dispose();
 }

就足够了。

如果您的使用者未能 Dispose 您的对象,它通常会在没有调用 Dispose 的情况下被 GC 处理,_Writer 持有的对象也将被 GC 处理,并且它将有一个终结器,因此它仍然可以正确清理其非托管资源。

编辑

在对 Matt 和其他人提供的链接进行了一些研究后,我得出的结论是,我的答案站在这里。这就是为什么:-

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

然而,在现实世界中,我们绝大多数 .NET 开发人员从来没有靠近过非托管资源。如果你必须量化上面的“可能”,你会为你的 .NET 编码想出什么概率数字?

假设我有一个 Person 类型(为了论证,它的一个字段中有一个一次性类型,因此本身应该是一次性的)。现在我有继承者 Customer、Employee 等。如果有人继承 Person 并想要持有非托管资源,我用这个“模式”来混淆 Person 类真的合理吗?

有时,我们的开发人员可能会将事情过度复杂化,以尝试针对所有可能的情况进行编码,而无需使用关于此类情况的相对概率的一些常识。

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

呸,很高兴把它从我的胸口拿走。;)

于 2009-07-16T08:47:05.980 回答
17

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

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

编辑:

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

于 2009-07-16T08:47:55.107 回答
4

推荐的做法是仅当您拥有非托管资源(例如本机文件句柄、内存指针等)时才使用终结器。

不过我有两个小建议,

m_Disposed如果您以前调用Dispose 过您的托管资源,则不必使用“ ”变量来测试。你可以像这样使用类似的代码,

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

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

此外,如果您只是想将文本写入文件,请查看File.WriteAllTextFile.OpenTextFile.AppendText至是专门针对文本文件的ASCIIEncoding.

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

于 2009-07-16T08:51:18.237 回答
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();
    }
}

注意 try...catch; 从技术上讲,StreamWriter构造函数可能会抛出异常,在这种情况下,它永远不会获得所有权,FileStream您必须自己处理它。

如果您真的在使用 many IDisposables,请考虑将该代码放在 C++/CLI 中:这将为您生成所有样板代码(它透明地对本机和托管对象使用适当的确定性销毁技术)。

Wikipedia 有一个不错的 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 实现中的异常都很健壮(不应该发生,但仍然存在),并且在面对更多IDisposable成员或本机资源时易于维护.

并不是说我是 C++/CLI 的忠实粉丝,但是对于大量面向资源的代码来说,它很容易被 C# 击败,并且它与托管代码和本机代码具有绝对出色的互操作性——简而言之,完美的胶水代码 ;-)。我倾向于用 C# 编写 90% 的代码,但使用 C++/CLI 来满足所有互操作需求(尤其是如果你想调用任何 win32 函数 - MarshalAs 和其他地方的互操作属性令人恐惧且完全难以理解)。

于 2009-07-20T12:42:37.767 回答
1

在尝试处置它之前,您应该检查它_Writer不是。null(看起来不太可能null,但以防万一!)

protected virtual void Dispose(bool disposing)
{
    if (!_Disposed)
    {
        if (disposing && (_Writer != null))
        {
            _Writer.Dispose();
        }
        _Writer = null;
        _Disposed = true;
    }
}
于 2009-07-16T09:59:13.123 回答
0

如果你打开 StreamWriter,你也必须 Dispose 它,否则你会有泄漏。

于 2009-07-16T08:36:25.987 回答
0

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

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

  2. 如果你要这样做,最好把它放在 if 中 dispose 的位置。它可能不会有太大的不同,但是当由终结器处理时,您通常不应该使用托管对象(正如其他人所指出的那样,在这种情况下无论如何您都不需要)。

于 2009-07-16T15:10:59.480 回答