如何在.net中配置一个类?
Posted
技术标签:
【中文标题】如何在.net中配置一个类?【英文标题】:How to dispose a class in .net? 【发布时间】:2010-09-05 22:58:05 【问题描述】:.NET 垃圾收集器最终会释放内存,但如果您希望立即恢复该内存怎么办?类MyClass
调用需要用到什么代码
MyClass.Dispose()
并通过MyClass
中的变量和对象释放所有使用的空间?
【问题讨论】:
【参考方案1】:This article 有一个非常简单的演练。但是,不得不调用 GC 而不是让它自然运行通常是设计/内存管理不佳的标志,尤其是如果没有消耗有限的资源(连接,句柄,通常会导致实现 IDisposable 的任何其他内容)。
是什么导致您需要这样做?
【讨论】:
【参考方案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
【讨论】:
【参考方案3】:如果 MyClass 实现了 IDisposable,您就可以这样做。
MyClass.Dispose();
C# 的最佳实践是:
using( MyClass x = new MyClass() )
//do stuff
因为它在 try-finally 中结束了 dispose 并确保它永远不会错过。
【讨论】:
【参考方案4】:如果你不想(或不能)在你的类上实现 IDisposable,你可以像这样强制垃圾回收(但它很慢)-
GC.Collect();
【讨论】:
【参考方案5】:IDisposable 与释放内存无关。 IDisposable 是一种用于释放 非托管 资源的模式——而内存绝对是一种托管资源。
指向 GC.Collect() 的链接是正确答案,尽管 Microsoft .NET 文档通常不鼓励使用此函数。
编辑:已经为这个答案赢得了大量的业力,我觉得有一定的责任详细说明它,以免.NET资源管理的新手产生错误的印象。
在 .NET 进程中,有两种资源——托管和非托管。 “托管”意味着运行时控制资源,而“非托管”意味着它是程序员的责任。今天,我们在.NET 中真正关心的托管资源只有一种——内存。程序员告诉运行时分配内存,然后由运行时确定何时可以释放内存。 .NET 用于此目的的机制称为garbage collection,您只需使用 Google 就可以在 Internet 上找到大量有关 GC 的信息。
对于其他类型的资源,.NET 对清理它们一无所知,因此它必须依靠程序员来做正确的事情。为此,平台为程序员提供了三个工具:
-
VB 和 C# 中的 IDisposable 接口和“使用”语句
终结者
许多 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”自动关闭文件,则此代码不能泄漏文件资源。但是“tmp”对象本身的内存可能仍然被分配。那是因为 IDisposable 接口与垃圾收集器完全没有联系。如果你确实想确保内存被释放,你唯一的选择是调用GC.Collect()
来强制垃圾回收。
但是,无论如何强调这可能不是一个好主意。通常让垃圾收集器完成它的设计目的会更好,即管理内存。
如果资源的使用时间较长,以至于其生命周期跨越了多种方法,会发生什么情况?显然,“using”语句不再适用,因此程序员在使用完资源后必须手动调用“Dispose”。如果程序员忘记了会发生什么?如果没有回退,则进程或计算机最终可能会用尽任何未正确释放的资源。
这就是终结器的用武之地。终结器是您的类中与垃圾收集器有特殊关系的方法。 GC 承诺——在为该类型的任何对象释放内存之前——它将首先给终结器一个机会进行某种清理。
所以在文件的情况下,理论上我们根本不需要手动关闭文件。我们可以等到垃圾收集器到达它,然后让终结器完成工作。不幸的是,这在实践中效果不佳,因为垃圾收集器运行不确定。该文件可能保持打开的时间比程序员预期的要长得多。如果有足够多的文件保持打开状态,系统在尝试打开其他文件时可能会失败。
对于大多数资源,我们需要这两样东西。我们希望约定能够说“我们现在已经完成了这个资源”,并且我们希望确保如果我们忘记手动进行清理,至少有一些机会自动进行清理。这就是“IDisposable”模式发挥作用的地方。这是一个约定,它允许 IDispose 和终结器很好地配合使用。您可以通过查看 official documentation for IDisposable 来了解该模式的工作原理。
底线:如果您真正想做的只是确保释放内存,那么 IDisposable 和终结器将无济于事。但是 IDisposable 接口是所有 .NET 程序员都应该理解的极其重要的模式的一部分。
【讨论】:
我只是想指出这个问题及其答案存在术语问题。你们都在谈论释放/处置对象;即类的实例。类是类型本身、相关的代码——过程、函数、属性等——以及共享的(静态)类数据。从类本身释放内存或其他资源比实例的要困难得多,因为它涉及卸载该类所在的整个 AppDomain,以及它的程序集和其他相关类。当然,这不是人们通常想做的事情。 @Curl,如果您包含一些代码示例,那就太好了。【参考方案6】:还提一下 dispose 并不总是指内存是否合适?我比内存更频繁地处理资源,例如对文件的引用。 GC.Collect() 直接与 CLR 垃圾收集器相关,可能会也可能不会释放内存(在任务管理器中)。它可能会对您的应用程序产生负面影响(例如性能)。
归根结底,您为什么要立即恢复记忆?如果有来自其他地方的内存压力,操作系统会在大多数情况下为您提供内存。
【讨论】:
【参考方案7】:您不能真正强制 GC 在您需要时清理对象,尽管有一些方法可以强制它运行,但没有什么说它会清理您想要/期望的所有对象。最好以 try catch ex finally dispose end try (VB.NET rulz) 方式调用 dispose。但是 Dispose 用于清理对象以确定性方式分配的系统资源(内存、句柄、数据库连接等)。Dispose 不会(也不能)清理对象本身使用的内存,只有 GC可以的。
【讨论】:
【参考方案8】:您只能释放实现 IDisposable 接口的实例。
强制垃圾回收立即释放(非托管)内存:
GC.Collect();
GC.WaitForPendingFinalizers();
这通常是不好的做法,但例如在 x64 版本的 .NET 框架中存在一个错误,它使 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
,你想表明你已经完成了。你只是分配为空吗?你说的和实现 IDisposable 一样吗?解释我想问什么并不容易。基本上我有一个循环,它在每个循环上更新一个类,我想表明我已经完成了它。我是保持原样还是分配给null,还是实现它。 uisng
声明没有所有其他位(finiliser)。空处置方法。或者这是错的。
@Seabizkit 只有在你的类持有其他 IDisposable 的东西(并调用它们的 Dispose 方法),并且当你使用非托管资源之类的东西时,你才应该实现 IDisposable。如果您担心循环中的 GC 成本,也许您应该考虑将其设为值类型(结构),或者通过更改其工作方式以某种方式重用实例。【参考方案9】:
IDisposable 接口确实适用于包含非托管资源的类。如果您的类不包含非托管资源,为什么您需要在垃圾收集器释放资源之前释放资源?否则,只需确保您的对象尽可能晚地实例化并尽快超出范围。
【讨论】:
【参考方案10】:看看这个article
实现 Dispose 模式、IDisposable 和/或终结器与内存何时回收完全无关;相反,它与告诉 GC如何 回收该内存有关。当您调用 Dispose() 时,您绝不会与 GC 交互。
GC 只会在确定需要时运行(称为内存压力),然后(并且只有在那时)才会为未使用的对象释放内存并压缩内存空间。
你可以调用 GC.Collect() 但你真的不应该这样做,除非有一个非常很好的理由(几乎总是“从不”)。当您像这样强制执行带外收集周期时,实际上会导致 GC 做更多的工作,最终会损害您的应用程序性能。在 GC 收集周期期间,您的应用程序实际上处于冻结状态...运行的 GC 周期越多,您的应用程序冻结的时间就越长。
您还可以进行一些本机 Win32 API 调用来释放您的工作集,但即使是那些也应避免使用,除非有非常充分的理由这样做。
垃圾收集运行时背后的整个前提是,您无需(尽可能多地)担心运行时何时分配/取消分配实际内存;您只需要担心确保您的对象知道如何在被询问时自行清理。
【讨论】:
【参考方案11】:您可以在 c++ 中进行确定性对象销毁
你永远不想调用 GC.Collect,它会干扰垃圾收集器的自我调整以检测内存压力,并且在某些情况下除了增加堆上每个对象的当前代数之外什么也不做。
对于那些发布 IDisposable 答案的人。调用 Dispose 方法不会像询问者所描述的那样销毁对象。
【讨论】:
【参考方案12】:抱歉,此处选择的答案不正确。正如一些人随后所说的那样,Dispose 和实现 IDisposable 与释放与 .NET 类关联的内存无关。它主要和传统上用于释放非托管资源,例如文件句柄等。
虽然您的应用程序可以调用 GC.Collect() 来尝试强制垃圾收集器进行收集,但这只会对那些处于可访问队列中正确生成级别的项目产生影响。因此,如果您已经清除了对该对象的所有引用,那么在实际内存被释放之前,可能仍然需要对 GC.Collect() 进行几次调用。
你没有在你的问题中说为什么你觉得需要立即释放内存。我知道有时可能会出现异常情况,但严重的是,在托管代码中,最好让运行时处理内存管理。
如果您认为您的代码消耗内存的速度比 GC 释放它的速度更快,这可能是最好的建议,那么您应该检查您的代码以确保在您所在的任何数据结构中都没有引用不再需要的对象静态成员等。还要尽量避免出现循环对象引用的情况,因为这些可能也不会被释放。
【讨论】:
【参考方案13】:@Curt Hagenlocher - 回到前面。我不知道为什么有这么多人在它错的时候投赞成票。
IDisposable
用于托管资源。
终结者用于非托管资源。
只要您只使用托管资源,@Jon Limjap 和我自己都是完全正确的。
对于使用非托管资源的类(请记住,绝大多数 .Net 类不使用)Patrik 的回答是全面的最佳实践。
避免使用 GC.Collect - 这是一种处理托管资源的缓慢方法,并且除非您正确构建了 ~Finalizers,否则不会对非托管资源执行任何操作。
根据https://***.com/questions/14593/etiquette-for-modifying-posts,我已从原始问题中删除了版主评论
【讨论】:
【参考方案14】:@基思:
IDisposable 用于托管资源。
终结者用于非托管资源。
对不起,那是错误的。通常,终结器什么都不做。但是,如果 dispose pattern 已正确实现,则终结器会尝试调用 Dispose
。
Dispose
有两个工作:
您的陈述在这里发挥了作用,因为确实在完成时,对象不应该尝试释放嵌套的托管资源,因为这些资源可能已经被释放。但它仍然必须释放非托管资源。
不过,终结器除了调用Dispose
并告诉它不要接触托管对象之外没有其他工作。 Dispose
,当手动调用(或通过Using
)时,应释放所有非托管资源并将Dispose
消息传递给嵌套对象(和基类方法),但这将永远释放任何(托管)内存。
【讨论】:
【参考方案15】:Konrad Rudolph - 是的,通常终结者什么都不做。除非您正在处理非托管资源,否则不应实施它。
然后,当你实现它时,你使用Microsoft's dispose pattern(如前所述)
public Dispose()
调用 protected Dispose(true)
- 处理托管和非托管资源。调用 Dispose()
应该会抑制最终确定。
~Finalize
调用 protected Dispose(false)
- 仅处理非托管资源。如果您未能调用 public Dispose()
~Finalize
很慢,除非您确实有非托管资源要处理,否则不应使用。
托管资源不会发生内存泄漏,它们只会浪费当前应用程序的资源并减慢其垃圾收集速度。非托管资源可能会泄漏,~Finalize
是确保它们不会泄漏的最佳做法。
在任何一种情况下,using
都是最佳做法。
【讨论】:
【参考方案16】:Joe Duffy 对“Dispose, Finalization, and Resource Management”的完整解释:
在 .NET Framework 的早期版本中 终生,终结者始终如一 被 C# 称为析构函数 程序员。随着我们变得更聪明 时间,我们正在努力达成协议 Dispose 方法 真的更相当于一个 C++ 析构函数(确定性),而 终结器完全是一个东西 单独的(不确定的)。事实 C# 借用了 C++ 析构函数 语法(即〜T())肯定至少有 跟发展有点关系 这个用词不当。
【讨论】:
【参考方案17】:对这个问题的回答有点混乱。
标题询问了处置,但随后说他们想要立即恢复记忆。
.Net 是托管,这意味着当您编写 .Net 应用程序时,您无需直接担心内存,代价是您也无法直接控制内存。
.Net 决定何时最好清理和释放内存,而不是您作为 .Net 编码器。
Dispose
是一种告诉 .Net 您已完成某事的方式,但它实际上不会释放内存,直到它是这样做的最佳时机。
基本上,.Net 实际上会在最容易的时候收集内存——它非常擅长决定何时。除非你正在写一些非常占用内存的东西,否则你通常不需要推翻它(这是游戏不经常用 .Net 编写的部分原因——它们需要完全控制)
在 .Net 中,您可以使用 GC.Collect()
强制它立即执行,但这几乎总是不好的做法。如果 .Net 还没有清理它,那意味着现在不是清理的好时机。
GC.Collect()
拾取 .Net 识别为已完成的对象。如果您尚未处理需要它的对象,.Net 可能会决定保留该对象。这意味着GC.Collect()
仅在您正确实现一次性实例时才有效。
GC.Collect()
不是正确使用 IDisposable 的替代品。
所以 Dispose 和 memory 没有直接关系,但也没有必要。正确处理将使您的 .Net 应用程序更高效,因此使用更少的内存。
99% 的时间在 .Net 中以下是最佳实践:
规则 1:如果您不处理任何非托管或实现 IDisposable
的东西,则不必担心 Dispose。
规则 2:如果您有一个实现 IDisposable 的局部变量,请确保在当前范围内摆脱它:
//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
这并不完整,这就是示例被密封的原因。继承类可能需要遵守下一条规则...
规则 4: 如果类使用 非托管 资源,则实现 IDispose 并添加终结器。
.Net 不能对 unmanaged 资源做任何事情,所以现在我们讨论的是内存。如果你不清理它,你可能会出现内存泄漏。
Dispose 方法需要同时处理 managed 和 unmanaged 资源。
finaliser 是一个安全措施 - 它确保如果其他人创建了您的类的实例并且未能处理它,那么 .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;
请注意,一旦这一切就绪,其他创建类实例的托管代码就可以像对待任何其他 IDisposable 一样对待它(规则 2 和 3)。
【讨论】:
【参考方案18】:@基思,
我同意你的所有规则,除了#4。添加终结器只能在非常特定的情况下完成。如果一个类使用非托管资源,则应在 Dispose(bool) 函数中清理这些资源。当 bool 为真时,这个相同的函数应该只清理托管资源。添加终结器会增加使用对象的复杂性成本,因为每次创建新实例时,它还必须放在终结队列中,每次 GC 运行收集周期时都会检查该队列。实际上,这意味着您的对象在一个周期/代中的存活时间比它应该运行的时间长,因此可以运行终结器。终结器不应被视为“安全网”。
GC 只会在它确定 Gen0 堆中没有足够的可用内存来执行下一次分配时运行一个收集周期,除非你通过调用 GC.Collect() 来“帮助”它强制一个 out-of -乐队收藏。
底线是,无论如何,GC 只知道如何通过调用 Dispose 方法来释放资源(如果实现的话,可能还有终结器)。由该方法“做正确的事”并清理使用的任何非托管资源并指示任何其他托管资源调用其 Dispose 方法。只要没有带外收集周期的帮助,它的工作效率就很高,并且可以在很大程度上进行自我优化。话虽如此,如果没有显式调用 GC.Collect,您将无法控制何时以及以何种顺序处理对象和释放内存。
【讨论】:
【参考方案19】:在回答原始问题时,根据原始发布者迄今为止提供的信息,可以 100% 确定他对 .NET 编程的了解不够,甚至无法得到答案:使用 GC.Collect() .正如大多数海报所指出的那样,我会说他根本不需要使用 GC.Collect() 的可能性为 99.99%。
正确答案归结为“让 GC 完成其工作”。时期。你还有其他事情要担心。但是您可能需要考虑是否以及何时应该处置或清理特定对象,以及是否需要在您的类中实现 IDisposable 和可能的 Finalize。'
关于 Keith 的帖子和他的规则 #4:
有些海报混淆了规则 3 和规则 4。Keith 的规则 4 绝对正确,毫不含糊。这是四个规则中的一个,根本不需要编辑。我会稍微改写他的一些其他规则以使其更清晰,但如果您正确解析它们,它们基本上是正确的,并且实际上阅读了整篇文章以了解他如何扩展它们。
如果您的类不使用非托管资源,并且它也从不实例化本身直接或最终使用非托管对象的类的另一个对象(即,实现 IDisposable 的类),那么你的类不需要自己实现 IDisposable ,甚至不需要在任何东西上调用 .dispose 。 (在这种情况下,认为您实际上需要通过强制 GC 立即释放内存是愚蠢的。)
如果您的类使用非托管资源,或者实例化另一个本身实现 IDisposable 的对象,那么您的类应该:
a) 在创建它们的本地上下文中立即处置/释放它们,或者...
b) 按照 Keith 的帖子中推荐的模式实现 IDisposable,或者在互联网上的几千个地方,或者现在大约 300 本书中。
b.1) 此外,如果 (b) 是已打开的非托管资源,则 IDisposable 和 Finalize 都应始终按照 Keith 的规则 #4 实施。 在这种情况下,Finalize 在某种意义上绝对是一个安全网:如果有人实例化了使用非托管资源的 IDisposable 对象,并且他们未能调用 dispose,那么 Finalize 是您的对象正确关闭非托管资源的最后机会。 (Finalize 应该通过调用 Dispose 来执行此操作,即 Dispose 方法跳过释放除非托管资源之外的任何内容。或者,如果您的对象的 Dispose 方法被任何实例化您的对象正确调用,那么它都将 Dispose 调用传递给它已实例化的所有 IDisposable 对象,并正确释放非托管资源,并以抑制对对象的 Finalize 的调用结束,这意味着如果调用者正确处置对象,则使用 Finalize 的影响会降低。所有这些点包含在 Keith 的帖子中,顺便说一句。)
b.2) 如果你的类只实现 IDisposable 是因为它需要将 Dispose 传递给它已实例化的 IDisposable 对象,那么在这种情况下不要在你的类中实现 Finalize 方法。 Finalize 用于处理任何实例化对象都未调用 BOTH Dispose 并且使用了仍未释放的非托管资源的情况。
简而言之,关于基思的帖子,他是完全正确的,在我看来,那个帖子是最正确和最完整的答案。他可能会使用一些有些人认为“错误”或反对的简短陈述,但他的完整帖子完全扩展了 Finalize 的用法,他是绝对正确的。在跳到他的帖子中的规则或初步声明之一之前,请务必完整阅读他的帖子。
【讨论】:
感谢您指出我对 .Net 中的编程知之甚少,因此不应给出答案(即使不考虑在它仍在启动时提供 ***)!我猜你写答案是在深夜,而且通常会做得更好。 @Jorrit 你的名字是 Jon Galloway 还是 OP?【参考方案20】:我在http://codingcraftsman.wordpress.com/2012/04/25/to-dispose-or-not-to-dispose/写了Destructors和Dispose和Garbage collection的总结
回答原来的问题:
-
不要试图管理你的记忆
Dispose 不是关于内存管理,而是关于非托管资源管理
终结器是 Dispose 模式的固有部分,实际上会减慢托管对象的内存释放速度(因为它们必须进入终结队列,除非已经 Dispose d)
GC.Collect 不好,因为它会使一些短期对象看起来需要更长的时间,从而减慢它们被收集的速度。
但是,如果您有代码的性能关键部分并希望减少垃圾收集减慢它的可能性,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
对
myBigObject = null; // so it could now be collected
myBigObject = new MyBigObject(2);
但主要的答案是垃圾收集只是工作,除非你乱用它!
【讨论】:
以上是关于如何在.net中配置一个类?的主要内容,如果未能解决你的问题,请参考以下文章