【问题标题】:Where to call Dispose() of IDisposable created in constructor?在哪里调用构造函数中创建的 IDisposable 的 Dispose()?
【发布时间】:2014-09-29 10:33:53
【问题描述】:

在哪里为对象拥有的IDisposable 对象调用Dispose()

public class MyClass
{
    public MyClass()
    {
        log = new EventLog { Source = "MyLogSource", Log = "MyLog" };
        FileStream stream = File.Open("MyFile.txt", FileMode.OpenOrCreate);
    }


    private readonly EventLog log;
    private readonly FileStream stream;

    // Other members, using the fields above
}

我应该为这个例子实现Finalize() 吗?如果我什么都不做怎么办?会不会有什么问题?

我的第一个想法是MyClass 应该实现IDisposable。但是an MSDN article中的以下声明让我感到困惑:

仅当您直接使用非托管资源时才实施 IDisposable。如果您的应用程序仅使用实现 IDisposable,不要提供 IDisposable 实现。

这个说法有错吗?

【问题讨论】:

  • 累积。对于您的 MSDN 更新,您 正在 直接使用非托管资源。您通过将它们作为字段来拥有它们,因此您有责任处置它们。如果只使用它们(例如,作为对象传递给方法),则不应实现 IDisposable
  • 我同意 MSDN 的引用具有误导性。它谈论“您的应用程序”和“应用程序”(无论是什么)都无法实现 IDisposable。只有类可以。
  • @TimSchmelter:代码没有直接使用非托管资源;它正在获取和使用托管资源[托管资源包装器,理论上将被清理,最终,如果被放弃],这反过来又持有非托管资源。微软围绕资源的术语相当草率。

标签: c# .net dispose idisposable


【解决方案1】:

如果MyClass拥有一个IDisposable资源,那么MyClass本身应该是IDisposable,并且当Dispose()MyClass调用时它应该处理封装的资源:

public class MyClass : IDisposable {
    // ...
    public virtual void Dispose() {
        if(stream != null) {
            stream.Dispose();
            stream = null;
        }
        if(log != null) {
            log.Dispose();
            log = null;
        }
    }
}

不,你不应该在这里实现终结器。

注意:替代实现可能类似于:

private static void Dispose<T>(ref T obj) where T : class, IDisposable {
    if(obj != null) {
        try { obj.Dispose(); } catch {}
        obj = null;
    }
}

public virtual void Dispose() {
    Dispose(ref stream);
    Dispose(ref log);
}

【讨论】:

  • 如果设计符合您的建议并且 MyClass 实现了 IDisposable,那么对于不调用 Dispose 的用户,不应该在同一个 MyClass 中实现终结器。这将有助于防止内存泄漏或我遗漏了一些东西。仅用于非托管资源清理的最终确定。
  • 感谢您的回答。我对 MSDN 文档感到困惑,请参阅我对问题的更新,MSDN 错了吗?
  • @MrinalKamboj 是的,你错过了一些东西。终结器应该接触非托管资源。它们不得接触托管资源,因为收集顺序是不确定的:stream / log 对象可能已经被释放。 只有需要在这里做一些事情的处置。坦率地说,您曾经需要添加终结器的情况难以置信
  • @AndrejAdamenko 哇,是的,那个指导是完全错误的;这听起来像是从“终结器”页面复制/粘贴的。对于IDisposable,它归结为:谁拥有生命?在这种情况下,肯定是MyClass
  • @AndrejAdamenko .NET 4 指南没有这个错误:msdn.microsoft.com/en-us/library/…
【解决方案2】:

对于包含其他 IDisposable 对象的对象,it's a good and recommended practice to implement IDisposable on your own object,因此使用您的类型的其他人可以将其包装在 using 语句中:

public class MyClass : IDisposable
{
    public MyClass()
    {
        log = new EventLog { Source = "MyLogSource", Log="MyLog" };
        FileStream stream = File.Open("MyFile.txt", FileMode.OpenOrCreate);
    }


    private readonly EventLog log;
    private readonly FileStream stream;

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Free managed objects here
            stream.Dispose();
        }
    }

    // Other members, using the fields above
}

在您的情况下,您没有释放任何托管资源,因此不需要终结器。如果您,那么您将实现一个终结器并调用Dispose(false),向您的 dispose 方法指示它正在从终结器线程运行。

如果你没有实现IDisposable,你将把它留给GC来清理资源(例如,关闭你已经打开的FileStream上的Handle)一旦它启动了收藏。假设您的 MyClass 对象符合收集条件,并且当前处于第 1 代。您将保持 FileStream 句柄处于打开状态,直到 GC 运行后清理资源。此外,Dispose 的许多实现调用GC.SuppressFinalize 以避免让您的对象再进行一次 GC 循环,从初始化队列传递到 F-Reachable 队列。

【讨论】:

  • 感谢您的回答。我对 MSDN 文档感到困惑,请参阅我对问题的更新,MSDN 错了吗?
  • FileStream 不会在内部分配非托管资源。它将打开一个 FileHandle,如果 MyClass 的用户不调用 Dispose,则不需要清理。
  • @MrinalKamboj 确实如此。正如我所说,如果你不调用Dispose,它将留给FileStream 的终结器来运行和关闭打开的句柄。
  • @AndrejAdamenko 我不认为该指南是相关的,我不确定它是否是最新的。
  • @YuvalItzchakov 疯狂的是:.NET 4.0 版本中没有这种狡猾的指导;我认为有人错误地将“终结器”指南复制/粘贴到IDisposable 页面中。叹息。
【解决方案3】:

很多关于DisposeFinalize 的建议都是由那些期望Finalize 可以作为主要资源清理机制起作用的人编写的。经验表明,这种期望过于乐观。获取任何类型资源并在方法调用之间持有它们的面向公众的对象应该实现IDisposable,并且应该覆盖Finalize。如果一个对象拥有任何在被放弃时不会被清理的资源,它应该将每个这样的资源封装在一个 privately-held 对象中,然后应该使用Finalize 清理该资源,如果必填。

请注意,一个类通常不应该使用Finalize 来清理其他对象持有的资源。如果Finalize 在一个持有对另一个对象的引用的对象上运行,则通常会应用以下几个条件之一:

  • 不存在对其他对象的其他引用,并且它已经运行Finalize,因此该对象不需要执行任何操作来清理它。
  • 不存在对另一个对象的其他引用,它还没有运行Finalize,但计划这样做,所以这个对象不需要做任何事情来清理它。
  • 其他对象仍在使用其他对象,因此该对象不应尝试清理它。
  • 其他对象的清理方法无法在终结器线程的上下文中安全运行,因此该对象不应尝试清理它。
  • 这个对象只有资格运行Finalize,因为所有必要的清理已经完成,所以这个对象不需要做任何清理工作。

仅在可以理解为什么上述条件都不适用的情况下定义Finalize 方法。虽然存在这种情况,但很少见,最好不要使用Finalize 方法而不是使用不合适的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-29
    • 2021-07-17
    • 1970-01-01
    相关资源
    最近更新 更多