内存泄漏-C#调用IDisposable.Dispose()与使对象为null
本文关键字:对象 null -C# 泄漏 调用 IDisposable Dispose 内存 | 更新日期: 2023-09-27 17:50:48
考虑以下代码:
A。玩具类
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);
}
}
B。玩具插件
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方法。(请查看玩具类的评论(
IMHO,如果我将引用设为null,那么堆上的对象将被GC标记为Collection。
如果可以进行这种推理,堆和堆栈上会发生什么,以及GC在类(如ToyAddOn(实现IDisposable与不实现IDispasable时的作用,我们将不胜感激
现在有人建议我检查一个"having-a"对象是否实现IDisposable,然后调用它的dispose方法。
IDisposable
是一种模式,用于释放需要显式释放的非托管资源,因为GC并不知道这些资源。通常,持有非托管资源的类也会实现终结器,因此,它实际上非常渴望对象的生存期。在这些情况下,Dispose
通常调用对GC.SupressFinalize
的调用,以从最终队列中删除所述对象。
如果您有一个IDisposable
对象,那么自己实现IDisposable
以确保底层资源得到处理通常是一种很好的模式。
IMHO,如果我将引用设为null,那么堆上的对象将为标记为GC收集。
如果你想"null out"的成员不是static
成员,那么(通常(没有理由这么做。编译器足够聪明,可以对其进行优化,GC也足够聪明,知道不再有对变量的引用并将其清除。
编辑:
正如@ScottChamberlain所指出的,在某些情况下,一次性对象仍然是对的引用,因此GC不将其计入GC。当处理它并取消它时,您会提示GC它"已准备好进行收集"。
尝试将某个东西投射到IDisposable
并在它实现该接口时对其进行处理的模式在很大程度上表明存在设计缺陷。如果引用代码的类型不实现IDisposable
,那么这是一个非常强烈的指示:
-
该类型的引用的持有者不应该处理它;即使派生类型实现了
Disposable
,它们的清理责任也应该由创建这些类型的实例(并保存这些类型的引用(的代码来承担。 -
基类型应该已经实现了
IDisposable
(即使99%的实现什么都不做(,因为有些实例的生存期超出了任何知道它们是否真的需要清理的人的控制范围。后一种情况的一个主要例子是IEnumerator<T>
。尽管IEnumerator
省略了IDisposable
,但在发布后不久,对它的需求就变得明显了;当微软添加CCD_ 14时,他们直接包含了CCD_。尽管IEnumerator<T>
的大多数实现都可以在不进行清理的情况下放弃,但在集合上调用GetEnumerator
的代码不可能知道IEnumerator<T>
的实现是否是少数需要清理的实现之一,而且GetEnumerator
方法创建IEnumerator<T>
的IEnumerable<T>
也不可能知道客户端何时完成它,因此除了客户端之外没有人将处于CCD_ 22或CCD_。
确定实现Disposable
的ToyAddOn
的任何实例是否有可能在唯一引用由不知道是否需要清理的代码持有的时候结束其生存期。如果是,则使ToyAddOn
实现Disposable
。如果没有,请让知道需要清理的代码进行清理。
我同意这三个答案。只是为了简化您问题的答案:
/**有人说:这可能会导致内存泄漏*如果该类实现IDisposable,*那么我们不会处理任何资源**///this.addOn=null;
有人说的话不太适用于你的例子。但总的来说,这是绝对正确的
假设玩具类的寿命比ToyAddon长得多。ToyAddon班有一个活动订阅玩具班。如果是这样,ToyAddon应该在其Dispose方法中取消订阅这些订阅。如果在从Toy实例中删除ToyAddon的Dispose方法后没有调用该方法,那么ToyAddon-instance将在Toy实例存在的时间内一直存在于内存中。
而且,即使您在上面的情况下调用Dispose,仍然存在通过"addOn"对ToyAddon的引用。因此,需要将addOn设置为null才能完全消除它。
IMHO,如果我将引用设为null,那么堆上的对象将为标记为GC收集。
在你的情况下是正确的,正如我上面解释的一般是错误的。
如果不手动处置类,则只有当GC检测到内存压力足够,需要进行垃圾收集,并且对象被GCed和Finalized时,它所拥有的非托管资源(如果有的话(才会被清理。
并不是所有需要处理的东西都会增加内存压力,它们可能会保留诸如Window Handles之类的东西。
如果内存压力从未变得足够高,那么在耗尽dispose释放的资源之前,可能永远不会对这些值进行垃圾收集。当人们把位图放在一个紧密的循环中但不处理它们时,你经常会看到这种情况,每个位图都使用一个GDI+句柄,不计入托管内存压力,也不会触发GC。当人们仍然有足够的内存时,这通常会导致他们出现OutOfMemoryException,因为他们真正用完的是GDI+句柄。
通过显式地处理资源,您不需要"希望自己运气好",也不需要在对象所拥有的非托管资源用完之前完成对象。
堆叠与此无关。