.NET垃圾回收器最终会释放内存,但是如果您想立即获得该内存怎么办?在类 MyClass
中调用什么代码?
MyClass.Dispose()
如何释放MyClass
中变量和对象占用的所有空间?
IDisposable与释放内存无关。IDisposable是一种用于释放未托管资源的模式,而内存绝对是托管资源。
指向GC.Collect()的链接是正确的答案,但Microsoft .NET文档通常不鼓励使用此函数。
编辑:因为我为这个答案赚取了大量Karma,所以我感觉有责任对其进行阐述,以免.NET资源管理的新手产生错误印象。
在.NET进程内,有两种资源——托管和未托管。 "托管"表示运行时控制资源,而"未托管"则意味着由程序员负责。 在.NET今天真正关心的托管资源中,确实只有一种——内存。程序员告诉运行时分配内存,之后由运行时决定何时释放内存。 .NET用于此目的的机制称为垃圾回收,使用谷歌就可以在互联网上找到大量关于GC的信息。
对于其他类型的资源,.NET不知道如何清理它们,因此必须依靠程序员做正确的事情。 为此,平台为程序员提供了三个工具:
其中第一个允许程序员在同一方法中高效地获取资源、使用它并释放它。
using (DisposableObject tmp = DisposableObject.AcquireResource()) {
// Do something with tmp
}
// At this point, tmp.Dispose() will automatically have been called
// BUT, tmp may still a perfectly valid object that still takes up memory
如果“AcquireResource”是一个工厂方法,例如打开文件,而“Dispose”会自动关闭该文件,那么这段代码就不会泄漏文件资源。但是,“tmp”对象本身的内存可能仍然被分配。这是因为IDisposable接口与垃圾收集器没有任何联系。如果您确实希望确保释放了内存,则唯一的选择是调用GC.Collect()
强制进行垃圾回收。
然而,无论如何都不能强调这不是一个好主意。最好让垃圾收集器做它设计的事情,即管理内存。
如果资源被使用的时间更长,其寿命跨越多个方法,会发生什么?显然,“using”语句不再适用,因此当程序员完成资源使用时,必须手动调用“Dispose”。如果程序员忘记了怎么办?如果没有后备方案,那么进程或计算机最终可能会耗尽未正确释放的资源。
这就是finalizer的作用。finalizer是类上的一个方法,与垃圾收集器有特殊关系。GC承诺-在释放该类型任何对象的内存之前-首先给finalizer一个机会进行某种清理。
因此,在文件的情况下,理论上我们根本不需要手动关闭文件。我们可以等待垃圾收集器到达并让finalizer完成工作。不幸的是,这在实践中效果不佳,因为垃圾收集器运行是非确定性的。文件可能会保持打开状态比程序员预期时间更长。如果保持打开的文件足够多,当尝试打开另一个文件时,系统可能会失败。
对于大多数资源,我们想要这两个东西。我们想要一种约定,以便能够说“我们现在已经完成了这个资源”,并且我们想确保即使忘记手动清理,也至少有一些机会自动进行清理。这就是IDisposable模式发挥作用的地方。这是一种约定,允许IDispose和finalizer友好地协作。您可以查看IDisposable官方文档了解该模式的工作原理。
最重要的是:如果您真正想做的就是确保内存被释放,那么IDisposable和finalizer将无法帮助您。但是,IDisposable接口是所有.NET程序员都应该了解的极其重要的模式的一部分。
只有实现IDisposable接口的实例才能被处理。
强制进行垃圾回收以立即释放(非托管的)内存:
GC.Collect();
GC.WaitForPendingFinalizers();
通常情况下这是不好的做法,但例如在.NET框架的x64版中存在一个bug,可能会导致GC在某些场景下表现异常,那么您可能需要这样做。我不知道这个bug是否已经解决了。有人知道吗?
要释放一个类,您可以这样做:
instance.Dispose();
或者像这样:
using(MyClass instance = new MyClass())
{
// Your cool code.
}
这将在编译时转换为:
MyClass instance = null;
try
{
instance = new MyClass();
// Your cool code.
}
finally
{
if(instance != null)
instance.Dispose();
}
你可以这样实现IDisposable接口:
public class MyClass : IDisposable
{
private bool disposed;
/// <summary>
/// Construction
/// </summary>
public MyClass()
{
}
/// <summary>
/// Destructor
/// </summary>
~MyClass()
{
this.Dispose(false);
}
/// <summary>
/// The dispose method that implements IDisposable.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// The virtual dispose method that allows
/// classes inherithed from this one to dispose their resources.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources here.
}
// Dispose unmanaged resources here.
}
disposed = true;
}
}
MyClass
,您想要表明您已经完成了它。您只需分配为null吗?您是说这与实现IDisposable相同吗?很难解释我要问什么。基本上,我有一个循环,每次循环都会新建一个类,我想表明我已经完成了它。我应该保持原样还是分配为null,或者实现没有所有其他位(终结器)的using
语句。空的dispose方法。 这样做是错误的吗? - Seabizkit//using is best practice
using( SqlConnection con = new SqlConnection("my con str" ) )
{
//do stuff
}
//this is what 'using' actually compiles to:
SqlConnection con = new SqlConnection("my con str" ) ;
try
{
//do stuff
}
finally
{
con.Dispose();
}
规则3:如果一个类有实现IDisposable接口的属性或成员变量,则该类也应该实现IDisposable接口。在该类的Dispose方法中,你还可以处理你的IDisposable属性:
//rather basic example
public sealed MyClass :
IDisposable
{
//this connection is disposable
public SqlConnection MyConnection { get; set; }
//make sure this gets rid of it too
public Dispose()
{
//if we still have a connection dispose it
if( MyConnection != null )
MyConnection.Dispose();
//note that the connection might have already been disposed
//always write disposals so that they can be called again
}
}
~MyClass()
{
//calls a protected method
//the false tells this method
//not to bother with managed
//resources
this.Dispose(false);
}
public void Dispose()
{
//calls the same method
//passed true to tell it to
//clean up managed and unmanaged
this.Dispose(true);
//as dispose has been correctly
//called we don't need the
//'backup' finaliser
GC.SuppressFinalize(this);
}
最后是带有布尔标志的Dispose的重载:
protected virtual void Dispose(bool disposing)
{
//check this hasn't been called already
//remember that Dispose can be called again
if (!disposed)
{
//this is passed true in the regular Dispose
if (disposing)
{
// Dispose managed resources here.
}
//both regular Dispose and the finaliser
//will hit this code
// Dispose unmanaged resources here.
}
disposed = true;
}
是否适当提到,dispose 不仅仅指内存?我更经常处理文件引用等资源的处理。GC.Collect() 与CLR垃圾回收器直接相关,可能会释放内存(在任务管理器中)。它很可能会对应用程序产生负面影响(例如性能)。
总之,为什么您希望立即回收内存呢?如果来自其他地方的内存压力存在,大多数情况下操作系统会为您获取内存。
var myBigObject = new MyBigObject(1);
// something happens
myBigObject = new MyBigObject(2);
// at the above line, there are temporarily two big objects in memory and neither can be collected
vs
myBigObject = null; // so it could now be collected
myBigObject = new MyBigObject(2);
public class MyClass : IDisposable
{
public void Dispose()
{
// cleanup here
}
}
MyClass todispose = new MyClass();
todispose.Dispose(); // instance is disposed right here
或者
using (MyClass instance = new MyClass())
{
}
// instance will be disposed right here as it goes out of scope
Joe Duffy对“Dispose,Finalization和Resource Management”进行了完整的解释:
在.NET Framework的早期,C#程序员通常将finalizers称为析构函数。随着时间的推移,我们正试图认识到Dispose方法实际上更类似于C++析构函数(确定性),而finalizer是完全不同的东西(非确定性)。C#借鉴了C++析构函数语法(即~T()),这种误称的发展肯定至少与此有点关系。
虽然有方法可以强制运行垃圾回收,但你并不能真正强制清除想要的对象。最好的方法是使用try catch ex finally dispose end try(VB.NET规则)调用Dispose来清理由对象分配的系统资源(内存、句柄、数据库连接等)。但Dispose仅能以确定性方式清理系统资源,无法清理对象本身使用的内存,只有垃圾回收器才能做到。
这篇文章提供了一个非常简单的指南。然而,需要调用GC而不是让它自然运行通常意味着糟糕的设计/内存管理,尤其是如果没有消耗有限资源(连接、句柄或其他通常导致实现IDisposable的东西)。
是什么导致你需要这样做呢?