如何在.net中处理一个类?

.NET垃圾收集器最终将释放内存,但是如果您立即想要回收内存呢? 您需要在类MyClass中调用哪些代码

 MyClass.Dispose() 

并通过MyClassvariables和对象释放所有使用的空间?

IDisposable与释放内存无关。 IDisposable是一种释放非托pipe资源的模式 – 而内存绝对是一种托pipe资源。

指向GC.Collect()的链接是正确的答案,尽pipeMicrosoft .NET文档通常不鼓励使用此函数。

编辑:为这个答案赢得了大量的业力,我觉得有一定的责任要详细说明,免得一个.NET资源pipe理的新手得到错误的印象。

在.NET进程中,有两种资源pipe理和非托pipe。 “托pipe”是指运行时在控制资源,而“非托pipe”则意味着程序员的责任。 在.NET中,我们真正关心的只有一种托pipe资源 – 内存。 程序员通知运行时分配内存,然后由运行时决定何时可以释放内存。 .NET用于此目的的机制称为垃圾收集 ,您只需使用Google即可在Internet上find有关GC的大量信息。

对于其他types的资源,.NET并不知道如何清理它们,所以它必须依赖程序员来做正确的事情。 为此,平台给程序员三个工具:

  1. VB和C#中的IDisposable接口和“using”语句
  2. 终结
  3. 由许多BCL类实现的IDisposable模式

第一种方法允许程序员有效地获取资源,使用它,然后在相同的方法中释放它。

 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”自动closures文件,那么这个代码不能泄漏文件资源。 但“tmp”对象本身的内存可能仍然可以分配。 这是因为IDisposable接口完全没有连接到垃圾收集器。 如果你确实想确保内存被释放,你唯一的select就是调用GC.Collect()来强制垃圾收集。

但是,不能强调这可能不是一个好主意。 让垃圾收集器按照devise要做的事来处理内存通常要好得多。

如果资源使用时间较长,会发生什么情况,使得它的使用寿命跨越几种方法? 显然,“使用”语句不再适用,所以程序员在完成资源处理时必须手动调用“Dispose”。 如果程序员忘记了会发生什么? 如果没有回退,那么进程或计算机可能最终用完没有被正确释放的资源。

这就是finalizer的作用。finalizer是类中与垃圾收集器有特殊关系的方法。 GC承诺 – 在为这种types的任何对象释放内存之前,它会首先给终结器一个清理的机会。

所以在文件的情况下,我们理论上根本不需要手动closures文件。 我们可以等到垃圾收集器到达它,然后让终结器完成这项工作。 不幸的是,这在实践中效果不好,因为垃圾收集器运行不确定。 该文件可能保持打开比程序员期望更长的时间。 如果有足够的文件保持打开状态,则系统可能会在尝试打开其他文件时失败。

对于大多数资源,我们都需要这些东西。 我们希望一个会议能够说“我们现在已经完成了这个资源”,并且我们要确保如果我们忘记手动完成清理工作,至less有一些机会会自动发生。 这就是“IDisposable”模式发挥作用的地方。 这是一个允许IDispose和终结器一起玩的好习惯。 您可以通过查看IDisposable的官方文档来了解模式的工作原理。

底线:如果你真的想要做的就是确保内存被释放,那么IDisposable和终结器将不会帮助你。 但是IDisposable接口是所有.NET程序员都应该理解的一个非常重要的模式的一部分。

您只能处理实现IDisposable接口的实例。

强制垃圾收集立即释放(非托pipe)内存:

 GC.Collect(); GC.WaitForPendingFinalizers(); 

这通常是不好的做法,但是在.NET框架的x64版本中存在一个错误,这使得GC在某些情况下performance得很奇怪,然后您可能需要这样做。 我不知道这个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; } } 

对这个问题的回答已经不止一点困惑了。

标题询问关于处理,但是然后说他们立刻要回忆内存。

.Net是被pipe理的 ,这意味着当你编写.Net应用程序时,你不需要直接担心内存,代价就是你没有对内存的直接控制。

.Net决定什么时候最好清理和释放内存,而不是.Net编码器。

Dispose是告诉.Net的一种方式,你已经完成了一些事情,但是直到现在是最好的时候才会释放内存。

基本上,.net会在最容易的时候回收内存 – 这是很好的决定时间。 除非你正在写内存密集的东西,否则你通常不需要推翻它(这是游戏经常不用.NET编写的原因之一 – 它们需要完全控制)

在.Net中,您可以使用GC.Collect()立即强制执行它,但这几乎总是不好的做法。 如果.Net没有清理它,那意味着它不是一个特别好的时机。

GC.Collect()获取.Net标识的对象。 如果你没有放置一个需要它的对象,.Net可能决定保留这个对象。 这意味着GC.Collect()只有在您正确实现一次性实例的情况下才有效。

GC.Collect() 不能正确使用IDisposable。

所以configuration和内存不是直接相关的,但是不需要。 正确configuration将使您的.Net应用程序更高效,因此使用更less的内存。


在.Net中99%的时间以下是最佳实践:

规则1:如果您不处理任何非托pipe或实现IDisposable事情,那么不要担心Dispose。

规则2:如果你有一个实现IDisposable的局部variables,确保你在当前范围内摆脱它:

 //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的属性或成员variables,那么这个类也应该实现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 } } 

这并不完全,这就是为什么这个例子是封闭的。 inheritance类可能需要遵守下一条规则…

规则4:如果一个类使用非托pipe资源,则实现IDispose 添加一个终结器。

.net不能对非托pipe资源做任何事情,所以现在我们正在谈论内存。 如果你不清理它,你可能会发生内存泄漏。

Dispose方法需要处理托pipe非托pipe资源。

终结者是一个安全漏洞 – 它确保了如果有其他人创build了你的class级的实例并且未能处理它,“危险的” 非托pipe资源仍然可以通过.Net清除。

 ~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; } 

请注意,一旦这一切都到位,其他托pipe代码创build您的类的实例可以像对待任何其他IDisposable(规则2和3)。

还要提到处置并不总是指内存吗? 我比内存更经常地处理资源这样对文件的引用。 GC.Collect()直接关联到CLR垃圾收集器,并可能释放或不释放内存(在任务pipe理器中)。 它可能会以负面的方式影响你的应用程序(例如性能)。

在一天结束时,你为什么要立即回忆记忆? 如果有来自其他地方的内存压力,操作系统将在大多数情况下获得内存。

看看这篇文章

当内存被回收时,实现Dispose模式,IDisposable和/或终结器完全没有关系; 相反,它告诉GC 如何回收这些内存,都是要做的。 当你调用Dispose()时,你决不会与GC交互。

GC只在确定需要(称为内存压力)时运行,然后(并且只有这样)才能为未使用的对象释放内存并压缩内存空间。

可以调用GC.Collect(),但是你真的不应该这样做,除非有一个很好的理由(几乎总是“从不”)。 当您强制执行这样的带外收集周期时,实际上会导致GC执行更多的工作,最终最终可能会损害应用程序的性能。 对于GC采集周期,您的应用程序实际上处于冻结状态…运行的GC循环越多,应用程序花费的时间就越多。

还有一些原生的Win32 API调用可以让你释放工作集,但是除非有很好的理由,否则即使这些调用也应该避免。

Gargbage收集运行时的整个前提是,您不必担心运行时何时分配/释放实际内存。 你只需要担心,确保你的对象知道如何清理后自己问。

 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#程序员称为析构函数。 随着时间的推移,我们变得越来越聪明,我们试图接受这样一个事实: Dispose方法实际上更像C ++析构函数(确定性的) ,而finalizer是完全独立的(非确定性的) 。 C#借用C ++析构函数语法(即〜T())的事实,至less与这个用词不当的发展有一点关系。

你不能真的强迫一个GC来清理一个对象,虽然有办法强制它运行,没有说它清理了你想要的所有对象。 最好调用一个try catch()函数,最后configuration一下(VB.NET rulz)的方式。 但是Dispose是用来清除由对象分配的系统资源(内存,句柄,数据库连接等),Dispose不会(也不能)清理对象本身使用的内存,只有GC可以做到这一点。

我在http://codingcraftsman.wordpress.com/2012/04/25/to-dispose-or-not-to-dispose/上写了一个Destructors和Dispose and Garbage collection的摘要

回答原来的问题:

  1. 不要试图pipe理你的记忆
  2. Dispose不是关于内存pipe理,而是关于非托pipe资源pipe理
  3. Finalizer是Dispose模式的先天部分,实际上减慢了释放托pipe对象的内存(因为它们必须进入Finalization队列,除非已经Dispose d)
  4. GC.Collect是不好的,因为它使得一些短暂的对象看起来需要更长的时间,因此减慢了收集的速度。

但是,如果您有一个性能严重的代码段,并希望减less垃圾收集的速度,则可以使用GC.Collect。 你以前打过电话

最重要的是,有一种观点支持这种模式:

 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); 

但主要的答案是,垃圾收集只是工作,除非你乱它!

这篇文章有一个非常简单的演练。 然而, 不得不调用GC而不是让其采取自然的过程通常是devise/内存pipe理不好的标志, 特别是在没有有限的资源被消耗的情况下(连接,句柄或其他任何通常导致实现IDisposable的事情)。

什么原因导致你需要这样做?

IDisposable接口实际上是用于包含非托pipe资源的类。 如果您的类不包含非托pipe资源,那么为什么在垃圾收集器执行之前需要释放资源? 否则,只要确保你的对象尽可能晚地实例化,并尽快超出范围。

对不起,但这里select的答案是不正确的。 正如一些人后来所说的那样Dispose和实现IDisposable与释放与.NET类关联的内存无关。 它主要和传统上用于释放文件句柄等非托pipe资源。

虽然你的应用程序可以调用GC.Collect()来试图通过垃圾收集器强制收集,但这只会影响到可扩展队列中正确生成级别的项目。 所以如果你已经清除了对象的所有引用,在实际的内存被释放之前,它可能仍然是GC.Collect()的一些调用。

你不要在你的问题中说,为什么你觉得有必要立即释放内存。 我明白,有时可能会出现exception情况,但是在托pipe代码中,几乎总是最好让运行时处理内存pipe理。

可能是最好的build议,如果你认为你的代码使用内存比GC更快地释放它,那么你应该检查你的代码,以确保没有任何不再需要的对象被任何数据结构引用,你有静态成员等。也要尽量避免你有循环对象引用的情况,因为这些引用可能不会被释放。

@Keith,

我同意除#4以外的所有规则。 添加终结者只能在非常特殊的情况下完成。 如果一个类使用非托pipe资源,那么应该在Dispose(bool)函数中清理这些资源。 当bool为true时,这个相同的函数只应该清理被pipe理的资源。 添加终结器会增加使用对象的复杂性成本,因为每次创build新实例时,都必须将其放置在终止队列中,每当GC运行一个收集周期时都会检查该终止队列。 实际上,这意味着你的对象比一个循环/一代更长的时间存活,所以终结器可以运行。 终结者不应被认为是一个“安全网”。

当GC确定Gen0堆中没有足够的可用内存来执行下一个分配时,GC将只运行一个收集周期,除非通过调用GC.Collect()来强制执行一个带外收集。

底线是,无论如何,GC只通过调用Dispose方法知道如何释放资源(如果实现的话也可能是终结器)。 这是由“做正确的事”的方法,并清理使用的任何非托pipe资源,并指示其他托pipe资源调用其Dispose方法。 它的function非常高效,只要没有带外收集周期的帮助,就可以在很大程度上进行自我优化。 这就是说,没有明确地调用GC.Collect,你无法控制对象何时以何种方式处理和释放内存。

如果你不想(或不能)在你的类上实现IDisposable,你可以像这样强制垃圾回收(但是速度很慢)

 GC.Collect(); 

你可以在c ++中有确定性的对象销毁

你永远不想调用GC.Collect,它会随着垃圾收集器的自我调整来检测内存压力,在某些情况下除了增加堆中每一个对象的当前代。

对于那些张贴IDisposable答案。 调用Dispose方法不会像提问者所描述的那样销毁对象。

@Keith:

IDisposable用于pipe理资源。

终结者是为非托pipe资源。

对不起,但这是错误的。 通常情况下,终结者什么都不做。 但是,如果已正确实施configuration模式 ,则终结器会尝试调用Dispose

Dispose有两个工作:

  • 免费的非托pipe资源,和
  • 自由嵌套的pipe理资源。

在这里你的陈述发挥作用,因为这是真的,虽然最终确定,一个对象不应该尝试释放嵌套的pipe理资源,因为这些资源可能已经被释放。 它仍然必须释放非托pipe资源。

尽pipe如此,终结器除了调用Dispose并且告诉它不要触摸pipe理对象外,没有任何工作。 当手动调用(或通过Using )时, Dispose将释放所有非托pipe资源,并将Dispose消息传递给嵌套对象(和基类方法),但这不会释放任何(托pipe)内存。

康拉德·鲁道夫 – 通常最终决定者什么都不做。 除非您正在处理非托pipe资源,否则不应执行它。

然后,当你实现它时,你使用微软的dispose模式 (如前所述)

  • public Dispose()调用protected Dispose(true) – 处理托pipe和非托pipe资源。 调用Dispose()应该禁止最终化。

  • ~Finalize调用protected Dispose(false) – 仅处理非托pipe资源。 如果您无法调用public Dispose()可以防止非托pipe内存泄漏

~Finalize是缓慢的,不应该使用,除非你有非托pipe资源来处理。

被pipe理的资源不能内存泄漏,他们只能浪费当前应用程序的资源,并减缓其垃圾收集。 非托pipe资源可能会泄漏,并且~Finalize是确保它们不是最佳实践。

在任何情况下using都是最佳实践。

@Curt Hagenlocher – 这是回到前面。 我不知道为什么有这么多人投了错,

IDisposable用于pipe理资源。

终结者是为非托pipe资源。

只要你只使用托pipe资源@Jon Limjap和我自己都是完全正确的。

对于那些使用非托pipe资源的类(并且记住绝大多数.Net类没有),Patrik的答案是全面和最好的实践。

避免使用GC.Collect – 这是处理托pipe资源的一种缓慢方式,除非已经正确构build了〜Finalizer,否则不会对非托pipe资源执行任何操作。


我已经从原来的问题删除了主持人评论符合https://stackoverflow.com/questions/14593/e​​tiquette-for-modifying-posts

在回答最初的问题时,用原来的海报给出的信息,100%确定他对.NET编程知之甚less,甚至得不到答案:使用GC.Collect()。 我想说的是99.99%的可能性,他根本不需要使用GC.Collect(),正如大多数海报所指出的那样。

正确的答案归结为“让GC工作。 期。 你还有其他的东西需要担心。 但是您可能要考虑是否以及何时应该处理或清理特定对象,以及是否需要在课堂上实现IDisposable和可能的Finalize。

关于基思的职位和他的规则#4:

有些海报令人迷惑,规则3和规则4。基思的规则4是绝对正确的,毫不含糊。 这是四个不需要编辑的一个规则。 我会稍微改述他的一些其他规则,使之更加清晰,但是如果你正确地parsing它们,它们本质上是正确的,并且实际阅读整篇文章来看看他是如何扩展它们的。

  1. 如果你的类没有使用非托pipe资源,也永远不会实例化一个类的另一个对象,这个对象直接或者最终是一个非托pipe对象(即实现IDisposable的类),那么就不需要你的类要么实现IDisposable本身,要么调用任何东西。 (在这种情况下,无论如何,认为你真的需要立即用强制GC来释放内存是愚蠢的。)

  2. 如果您的类使用非托pipe资源,或者实例化另一个自己实现IDisposable的对象,那么您的类应该:

    a)立即在创build它们的地方环境中处置/释放这些,或者…

    b)按Keith的post推荐的模式实现IDisposable,或者在互联网上的几千个地方实现IDisposable,或者到目前为止,大约有300本书。

    b.1)另外,如果(b),并且它是一个非托pipe资源已经被打开,则根据Keith的规则#4,应该一直执行IDisposable和Finalize。
    在这种情况下,从某种意义上说,Finalize绝对是一个安全networking:如果有人实例化了使用非托pipe资源的Your IDisposable对象,并且它们未能调用dispose,那么Finalize是您的对象正确closures非托pipe资源的最后机会。
    (Finalize应该这样做,通过调用Dispose来使Dispose方法跳过释放任何东西而非非托pipe资源。或者,如果你的对象的Dispose方法被任何实例化的对象所调用,那么它将把Di​​spose调用传递给所有已经实例化的IDisposable对象,并正确释放非托pipe资源,最后以一个调用来压制对象的Finalize,这意味着如果你的对象被调用者正确的处理,使用Finalize的影响会减less。包括在基思的职位,顺便说一句)。

    b.2)如果你的类只是实现了IDisposable,因为它实际上需要将一个Dispose传递给一个IDisposable对象,那么在这种情况下就不要在你的类中实现一个Finalize方法。 Finalize是为了处理这样的情况,即任何实例化对象都不会调用BOTH Dispose,并且还利用了尚未发布的非托pipe资源。

总之,关于Keith的post,他是完全正确的,而我认为这个post是最正确和最完整的答案。 他可能会用一些简短的陈述,有些人发现“错误”或反对,但是他的完整post完全扩展了Finalize的用法,他是绝对正确的。 请务必完整阅读他的post,然后跳到他的post中的规则或初步陈述之一。

如果MyClass实现了IDisposable,你可以做到这一点。

 MyClass.Dispose(); 

C#中的最佳实践是:

 using( MyClass x = new MyClass() ) { //do stuff } 

正如那个试用最终包装处置,并确保它永远不会错过。