通过阅读Microsoft文档,我知道IDisposable接口的“主要”用途是清理非托管资源。
对我来说,“非托管”意味着数据库连接、套接字、窗口句柄等。但是,我看到过一些代码,其中使用Dispose()方法来释放托管资源,这对我来说似乎是多余的,因为垃圾收集器应该为你负责。
例如:
public class MyCollection : IDisposable
{
private List<String> _theList = new List<String>();
private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();
// Die, clear it up! (free unmanaged resources)
public void Dispose()
{
_theList.clear();
_theDict.clear();
_theList = null;
_theDict = null;
}
}
我的问题是,这是否会使MyCollection使用的垃圾收集器释放内存比正常情况更快?
编辑:到目前为止,人们已经发布了一些使用IDisposable清理非托管资源(如数据库连接和位图)的好例子。但假设上述代码中的_theList包含一百万个字符串,并且您希望现在释放内存,而不是等待垃圾收集器。上面的代码能做到这一点吗?
我不再重复关于使用或释放非托管资源的通常内容,这些内容已经全部介绍过了。但我想指出一个常见的误解。给定以下代码
Public Class LargeStuff
Implements IDisposable
Private _Large as string()
'Some strange code that means _Large now contains several million long strings.
Public Sub Dispose() Implements IDisposable.Dispose
_Large=Nothing
End Sub
我意识到一次性实现没有遵循当前的指导原则,但希望你们都能理解。现在,当调用Dispose时,释放了多少内存?答:没有。调用Dispose可以释放非托管资源,它不能回收托管内存,只有GC可以这样做。这并不是说上述模式不是一个好主意,事实上遵循上述模式仍然是一个好想法。一旦Dispose运行完毕,没有什么可以阻止GC重新声明_Large使用的内存,即使LargeStuff的实例可能仍在范围内。_Large中的字符串也可能在第0代,但LargeStuff的实例可能在第2代,因此,内存将很快被重新占用。但是,添加一个finalizer来调用上面显示的Dispose方法是没有意义的。这将延迟内存的重新声明,以允许终结器运行。
是的,这段代码完全是多余和不必要的,它不会让垃圾回收器做任何它不会做的事情(即,一旦MyCollection的实例超出范围),尤其是.Clear()调用。
对编辑的回答:有点。如果我这样做:
public void WasteMemory()
{
var instance = new MyCollection(); // this one has no Dispose() method
instance.FillItWithAMillionStrings();
}
// 1 million strings are in memory, but marked for reclamation by the GC
在内存管理方面,它的功能与此相同:
public void WasteMemory()
{
var instance = new MyCollection(); // this one has your Dispose()
instance.FillItWithAMillionStrings();
instance.Dispose();
}
// 1 million strings are in memory, but marked for reclamation by the GC
如果您真的真的需要立即释放内存,请调用GC.Collect()。内存将在需要时释放。
Dispose模式的目的是提供一种清理托管和非托管资源的机制,何时发生取决于Dispose方法的调用方式。在您的示例中,Dispose的使用实际上并没有执行任何与Dispose相关的操作,因为清除列表对正在处理的集合没有影响。同样,将变量设置为null的调用对GC也没有影响。
关于如何实现Dispose模式的更多详细信息,您可以查看本文,但基本上如下所示:
public class SimpleCleanup : IDisposable
{
// some fields that require cleanup
private SafeHandle handle;
private bool disposed = false; // to detect redundant calls
public SimpleCleanup()
{
this.handle = /*...*/;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources.
if (handle != null)
{
handle.Dispose();
}
}
// Dispose unmanaged managed resources.
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
这里最重要的方法是Dispose(bool),它实际上在两种不同的情况下运行:
dispositing==true:用户代码直接或间接调用了该方法。可以释放托管和非托管资源。dispositing==false:该方法已由运行时从终结器内部调用,不应引用其他对象。只能释放非托管资源。
简单地让GC负责清理的问题是,你无法真正控制GC何时运行一个收集周期(你可以调用GC.Collect(),但你真的不应该这样做),所以资源可能会比需要的时间更长。记住,调用Dispose()实际上不会导致收集循环,也不会以任何方式导致GC收集/释放对象;它只是提供了更明确地清理所用资源的方法,并告诉GC该清理已经执行。
IDisposable和dispose模式的重点不是立即释放内存。对Dispose的调用实际上只有在处理dispositing==false场景和处理非托管资源时才有机会立即释放内存。对于托管代码,在GC运行一个收集循环之前,内存实际上不会被回收,这是您无法控制的(除了调用GC.Collect()之外,我已经提到过这不是一个好主意)。
由于.NET中的字符串不使用任何未更改的资源,也不实现IDisposable,因此您的方案并不真正有效,因此无法强制“清理”它们