C# 调用 IDisposable.Dispose() 与使对象为空

C# calling IDisposable.Dispose() vs making object null

考虑以下代码:

一个。玩具Class

class Toy
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    private ToyAddon addOn;
    public ToyAddon AddOn
    {
        get { return addOn; }
        set { addOn = value; }
    }

    public void RemoveAddons()
    {
        /*
         * someone said: this could lead to possible memory leak
         * if this class implements IDisposable,
         * then we are not disposing any of the resource 
         * */
        //this.addOn = null; 

        /* instead use this */
        var disposableToyAddOn = this.addOn as IDisposable;
        if (disposableToyAddOn != null)
            disposableToyAddOn.Dispose();

        this.addOn = null;

    }
    public override string ToString()
    {
        return String.Format("{0}, {1}",
            this.name, 
            (this.AddOn == null) ? "" : addOn.AddOnName);
    }
}

乙。玩具插件

class ToyAddon
{
    private string addOnName;

    public string AddOnName
    {
        get { return addOnName; }
        set { addOnName = value; }
    }

}

C。主程序

class Program
{
    static void Main(string[] args)
    {
        ToyAddon tAdd = new ToyAddon();
        tAdd.AddOnName = "Amazing AddOn";
        Toy t = new Toy();

        t.Name = "Amazing Toy";
        t.AddOn = tAdd;
        t.RemoveAddons();

        Console.WriteLine(t.ToString());


    }
}

现在有人建议我检查 "having-a" 对象是否正在实现 IDisposable,然后调用它的 dispose 方法。 (请查看玩具class中的评论)

恕我直言,如果我将引用设为空,那么堆上的对象将被标记为由 GC 收集。

如果可以进行此推理,堆和堆栈上发生的情况以及 GC 的作用,如果 class(如 ToyAddOn ) 实现 IDisposable 与不实现相比。

如果您不手动处理 class,它持有的非托管资源(如果有的话)只会在 GC 检测到有足够的内存压力需要垃圾收集和对象时被清理获得 GC 和最终确定。

并非所有需要处理的东西都会增加内存压力,它们可能会占用 Window 句柄之类的东西。

如果内存压力永远不会变得足够高,那么您可能永远不会在 运行 从处置释放的资源中垃圾收集这些值。当人们将 Bitmap 置于紧密循环中但不处置它们时,您经常会看到这种情况,每个位图都使用 GDI+ 句柄并且不计入托管内存压力并且不会触发 GC。这通常会导致人们在仍有大量内存时出现 OutOfMemoryExceptions,因为他们 运行 真正用掉的是 GDI+ 句柄。

通过显式处置您的资源,您不需要 "hope you get lucky" 并让您的对象在您 运行 之前完成其持有的非托管资源。

堆栈和堆与此无关。

Now it was suggested to me that check if a "having-a" object is implementing IDisposable then call the dispose method of it.

IDisposable 是一种模式,用于释放需要显式释放的非托管资源,因为 GC 不知道它们。通常,持有非托管资源的 class 也会实现终结器,因此它实际上延长了对象的生命周期,这比必要的要多。 Dispose 在这些情况下通常调用对 GC.SupressFinalize 的调用,以从终结队列中删除所述对象。

如果您有一个 IDisposable 对象,您自己实施 IDisposable 以确保底层资源得到处置通常是一个很好的模式。

IMHO, if I make the reference null, then the object on heap would be marked for Collection by GC.

如果您想要 "null out" 的成员不是 static 成员,那么(通常)没有理由这样做。编译器足够聪明,可以优化掉它,而 GC 也足够聪明,知道不再有对变量的引用并清理它。

编辑:

正如@ScottChamberlain 指出的那样,在某些情况下,一次性对象仍然持有引用,因此 GC 不会将其计入 GC。在处理它并将其清零时,您提示 GC 它是 "ready for collection".

尝试将某些东西转换为 IDisposable 并在它实现该接口时将其处置的模式高度表明存在设计缺陷。如果持有的参考代码类型未实现 IDisposable,则强烈表明:

  1. 持有该类型的参考资料的人不应该处理它;即使派生类型实现 Disposable,清理它们的责任也应该由创建这些类型实例(并保存这些类型的引用)的代码承担。

  2. 基本类型应该实现 IDisposable(即使 99% 的实现什么都不做)因为某些实例的生命周期 超出任何知道的人的控制范围他们是否真的需要清理。后一种情况的一个典型例子是 IEnumerator<T>。尽管 IEnumerator 省略了 IDisposable,但在发布后不久就很明显需要它;当 Microsoft 添加 IEnumerator<T> 时,他们直接包含了 IDisposable。尽管 IEnumerator<T> 的大多数实现可以在不进行清理的情况下被放弃,但是在集合上调用 GetEnumerator 的代码无法知道 IEnumerator<T> 的实现是否可能是其中之一需要清理的稀有对象,并且没有实际的方法 IEnumerable<T>GetEnumerator 方法创建 IEnumerator<T> 可以预期知道客户端何时完成它,因此除了客户端之外没有人会能够 Dispose IEnumerator<T>.

确定实现 DisposableToyAddOn 的任何实例是否有可能在生命周期结束时结束,而唯一的引用由不知道它们是否需要的代码持有清理。如果是这样,使 ToyAddOn 实现 Disposable。如果没有,请将清理留给知道它是必需的代码。

我同意发布的这三个答案。只是为了简化您问题的答案:

/* * someone said: this could lead to possible memory leak * if this class implements IDisposable, * then we are not disposing any of the resource * */ //this.addOn = null;

某人所说的不太适用于您的示例。但是总的来说是绝对正确的。

假设玩具 class 的寿命比 ToyAddon 长得多。 ToyAddon class 订阅了 Toy Class 的事件。如果是这样,ToyAddon 应该在其 Dispose 方法中取消订阅这些订阅。如果您在将 ToyAddon 从 Toy 实例中移除后没有调用它的 Dispose 方法,则只要 Toy 实例存在,ToyAddon 实例就会存在于内存中。

而且,即使您在上述情况下调用了 Dispose,仍然存在通过 'addOn' 对 ToyAddon 的引用。所以需要将addOn设置为null才能彻底去掉它。

IMHO, if I make the reference null, then the object on heap would be marked for Collection by GC.

你的情况是正确的,正如我上面所解释的,一般情况下是错误的。