提问人:ant2009 提问时间:5/23/2009 最后编辑:Niels R.ant2009 更新时间:11/25/2019 访问量:204870
在 C 语言中使用 Finalize/Dispose 方法#
Use of Finalize/Dispose method in C#
问:
C# 2008 (英语)
我已经研究了一段时间,我仍然对代码中 finalize 和 dispose 方法的使用感到困惑。我的问题如下:
我知道在处理非托管资源时,我们只需要一个终结器。但是,如果存在对非托管资源进行调用的托管资源,是否仍需要实现终结器?
但是,如果我开发一个不直接或间接使用任何非托管资源的类,我是否应该实现 以允许该类的客户端使用“using 语句”?
IDisposable
实现 IDisposable 只是为了使类的客户端能够使用 using 语句是否可行?
using(myClass objClass = new myClass()) { // Do stuff here }
我在下面开发了这个简单的代码来演示 Finalize/dispose 的使用:
public class NoGateway : IDisposable { private WebClient wc = null; public NoGateway() { wc = new WebClient(); wc.DownloadStringCompleted += wc_DownloadStringCompleted; } // Start the Async call to find if NoGateway is true or false public void NoGatewayStatus() { // Start the Async's download // Do other work here wc.DownloadStringAsync(new Uri(www.xxxx.xxx)); } private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // Do work here } // Dispose of the NoGateway object public void Dispose() { wc.DownloadStringCompleted -= wc_DownloadStringCompleted; wc.Dispose(); GC.SuppressFinalize(this); } }
关于源代码的问题:
这里我没有添加终结器,通常终结器会由 GC 调用,终结器会调用 Dispose。由于我没有终结器,因此何时调用 Dispose 方法?是类的客户端必须调用它吗?
因此,我在示例中的类称为 NoGateway,客户端可以像这样使用和释放该类:
using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here }
当执行到达 using 块的末尾时,是否会自动调用 Dispose 方法,或者客户端是否必须手动调用 Dispose 方法?即
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
我在课堂上使用该类。因为实现了接口,这是否意味着间接使用非托管资源?有没有硬性规定可以遵循?我如何知道一个类使用了非托管资源?
WebClient
NoGateway
WebClient
IDisposable
WebClient
答:
using(NoGateway objNoGateway = new NoGateway())
相当于
try
{
NoGateway = new NoGateway();
}
finally
{
NoGateway.Dispose();
}
当 GC 销毁您的对象时,会调用终结器。这可能与你离开方法时的时间完全不同。离开 using 块后,将立即调用 IDisposable 的 Dispose。因此,该模式通常是在不再需要资源后立即使用 using 来释放资源。
评论
1) WebClient 是托管类型,因此不需要终结器。如果你的用户没有 Dispose() 你的 NoGateway 类,并且本机类型(GC 不收集)之后需要清理,则需要终结器。在这种情况下,如果用户不调用 Dispose(),则 GC 将在 NoGateway 调用后立即释放包含的 WebClient。
2)间接是的,但你不必担心。您的代码是正确的,您无法阻止用户轻易忘记 Dispose()。
建议的 IDisposable 模式如下。对使用 IDisposable 的类进行编程时,通常应使用两种模式:
在实现不使用非托管资源的密封类时,只需实现 Dispose 方法,就像使用普通接口实现一样:
public sealed class A : IDisposable
{
public void Dispose()
{
// get rid of managed resources, call Dispose on member variables...
}
}
实现未密封的类时,请按如下方式执行:
public class B : IDisposable
{
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// get rid of managed resources
}
// get rid of unmanaged resources
}
// only if you use unmanaged resources directly in B
//~B()
//{
// Dispose(false);
//}
}
请注意,我没有在 ;仅当有实际的非托管资源要处置时,才应实现终结器。CLR 处理可终结对象的方式与处理不可终结对象的方式不同,即使调用了 CLR 也是如此。B
SuppressFinalize
因此,除非必要,否则不应声明终结器,但是如果类的继承者直接使用非托管资源,则可以为其提供一个钩子来调用 uer 并自己实现终结器:Dispose
public class C : B
{
private IntPtr m_Handle;
protected override void Dispose(bool disposing)
{
if (disposing)
{
// get rid of managed resources
}
ReleaseHandle(m_Handle);
base.Dispose(disposing);
}
~C() {
Dispose(false);
}
}
如果你没有直接使用非托管资源(朋友不算在内,因为他们声明了自己的终结器),那么就不要实现终结器,因为 GC 以不同的方式处理可终结的类,即使你后来禁止了终结器。另请注意,即使没有终结器,它仍然会调用正确处理任何实现终结器的子类。SafeHandle
B
SuppressFinalize
当类实现 IDisposable 接口时,这意味着在使用完该类后,应删除某些非托管资源。实际资源封装在类中;您无需显式删除它们。只需调用或将类包装在 a 中,即可确保在必要时删除任何非托管资源。Dispose()
using(...) {}
评论
IDisposable
如果您使用使用非托管资源的其他托管对象,则您没有责任确保这些对象最终确定。你的职责是在对你的对象调用 Dispose 时对这些对象调用 Dispose,并就此停止。
如果你的类不使用任何稀缺资源,我不明白你为什么要让你的类实现 IDisposable。只有在符合以下条件时,才应这样做:
- 知道你很快就会在你的对象中拥有稀缺的资源,只是不是现在(我的意思是,就像“我们仍在开发中,它会在我们完成之前出现”,而不是像“我认为我们需要这个”)
- 利用稀缺资源
是的,使用代码的代码必须调用对象的 Dispose 方法。是的,使用对象的代码可以如您所示使用。
using
(又是 2 个?WebClient 可能使用非托管资源或实现 IDisposable 的其他托管资源。然而,确切的原因并不重要。重要的是它实现了 IDisposable,因此,当你完成该对象的处理后,它就落在了你对该知识的处理上,即使事实证明 WebClient 根本不使用其他资源。
据我所知,强烈建议不要使用终结器/析构函数:
public ~MyClass() {
//dont use this
}
大多数情况下,这是由于不知道何时或是否会调用它。处置方法要好得多,特别是如果您直接使用或处置。
使用是好的。使用它:)
评论
请注意,任何 IDisposable 实现都应遵循以下模式(恕我直言)。我根据几个优秀的 .NET “神”的信息开发了此模式:.NET Framework 设计指南(请注意,MSDN 出于某种原因没有遵循此指南!.NET Framework 设计指南由 Krzysztof Cwalina(当时的 CLR 架构师)和 Brad Abrams(我相信当时的 CLR 项目经理)和 Bill Wagner([有效的 C#] 和 [更有效的 C#])编写(只需在 Amazon.com 上查看这些内容:
请注意,除非您的类直接包含(而不是继承)非托管资源,否则您永远不应实现终结器。一旦你在类中实现了一个终结器,即使它从未被调用,它也保证会为一个额外的集合而存在。它会自动放置在 Finalization Queue(在单个线程上运行)上。另外,一个非常重要的注意事项......在终结器中执行的所有代码(如果您需要实现一个)必须是线程安全和异常安全的!否则会发生坏事......(即未确定的行为,在出现异常的情况下,致命的不可恢复的应用程序崩溃)。
我整理的模式(并为其编写了代码片段)如下:
#region IDisposable implementation
//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable
// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }
/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
public void Dispose()
{
Dispose( true );
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
// Always use SuppressFinalize() in case a subclass
// of this type implements a finalizer.
GC.SuppressFinalize( this );
}
/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> equals false, the method has been called by the
/// runtime from inside the finalizer and you should not reference
/// other objects. Only unmanaged resources can be disposed.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
// TODO If you need thread safety, use a lock around these
// operations, as well as in your methods that use the resource.
try
{
if( !this.IsDisposed )
{
if( isDisposing )
{
// TODO Release all managed resources here
$end$
}
// TODO Release all unmanaged resources here
// TODO explicitly set root references to null to expressly tell the GarbageCollector
// that the resources have been disposed of and its ok to release the memory allocated for them.
}
}
finally
{
// explicitly call the base class Dispose implementation
base.Dispose( isDisposing );
this.IsDisposed = true;
}
}
//TODO Uncomment this code if this class will contain members which are UNmanaged
//
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
// ~$className$()
// {
// Dispose( false );
// }
#endregion IDisposable implementation
下面是在派生类中实现 IDisposable 的代码。请注意,不需要在派生类的定义中显式列出从 IDisposable 继承。
public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)
protected override void Dispose( bool isDisposing )
{
try
{
if ( !this.IsDisposed )
{
if ( isDisposing )
{
// Release all managed resources here
}
}
}
finally
{
// explicitly call the base class Dispose implementation
base.Dispose( isDisposing );
}
}
我已经在我的博客上发布了这个实现:如何正确实现处置模式
评论
Interlocked.Exchange
IsDisposed
没有人回答是否应该实现 IDisposable 的问题,即使你不需要它。
简短的回答 : 否
长答案:
这将允许类的使用者使用“using”。我想问的问题是——他们为什么要这样做?大多数开发人员不会使用“使用”,除非他们知道他们必须这样做——以及他们如何知道。也
- 它从经验中获取它们(例如套接字类)
- 它记录了
- 他们很谨慎,可以看到该类实现了 IDisposable
因此,通过实现 IDisposable,您告诉开发人员(至少是一些)这个类包含了一些必须发布的东西。他们将使用“使用”——但也有其他情况下无法使用(对象的范围不是本地的);在其他情况下,他们将不得不开始担心对象的寿命 - 我肯定会担心。但这不是必需的
您实现 Idisposable 以使他们能够使用 using,但除非您告诉他们,否则他们不会使用 use。
所以不要这样做
评论
我同意 pm100(并且应该在我之前的帖子中明确说明这一点)。
除非需要,否则不应在类中实现 IDisposable。具体来说,大约有 5 次需要/应该实现 IDisposable:
您的类显式包含(即不通过继承)任何实现 IDisposable 的托管资源,一旦不再使用您的类,就应该清理这些资源。例如,如果类包含 Stream、DbCommand、DataTable 等的实例。
您的类显式包含任何实现 Close() 方法的托管资源,例如 IDataReader、IDbConnection 等。请注意,其中一些类确实通过使用 Dispose() 和 Close() 方法实现 IDisposable。
您的类显式包含非托管资源 - 例如 COM 对象、指针(是的,您可以在托管 C# 中使用指针,但它们必须在“不安全”块中声明等。 对于非托管资源,还应确保在 RCW 上调用 System.Runtime.InteropServices.Marshal.ReleaseComObject()。尽管从理论上讲,RCW 是一个托管包装器,但仍在进行参考计数。
如果您的类使用强引用订阅事件。您需要取消注册/脱离事件。在尝试取消注册/分离它们之前,始终首先确保它们不是空的!
您的类包含上述内容的任意组合...
除了使用 COM 对象和必须使用 Marshal.ReleaseComObject() 之外,建议的替代方法是使用 System.Runtime.InteropServices.SafeHandle 类。
BCL(基类库团队)在这里有一篇关于它的很好的博客文章 http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
需要注意的一个非常重要的注意事项是,如果您正在使用 WCF 并清理资源,则几乎总是应该避免使用“using”块。MSDN上有很多博客文章,关于为什么这是一个坏主意。我也在这里发布了它 - 不要将“using()”与 WCF 代理一起使用
评论
要实施的官方模式很难理解。我相信这个更好:IDisposable
public class BetterDisposableClass : IDisposable {
public void Dispose() {
CleanUpManagedResources();
CleanUpNativeResources();
GC.SuppressFinalize(this);
}
protected virtual void CleanUpManagedResources() {
// ...
}
protected virtual void CleanUpNativeResources() {
// ...
}
~BetterDisposableClass() {
CleanUpNativeResources();
}
}
更好的解决方案是制定一条规则,即始终必须为需要处理的任何非托管资源创建包装类:
public class NativeDisposable : IDisposable {
public void Dispose() {
CleanUpNativeResource();
GC.SuppressFinalize(this);
}
protected virtual void CleanUpNativeResource() {
// ...
}
~NativeDisposable() {
CleanUpNativeResource();
}
}
对于 SafeHandle
及其衍生产品,这些类应该非常罕见。
对于不直接处理非托管资源的一次性类,即使存在继承,其结果也是强大的:它们不再需要关注非托管资源。它们将易于实现和理解:
public class ManagedDisposable : IDisposable {
public virtual void Dispose() {
// dispose of managed resources
}
}
评论
disposed
来自 msdn 的模式
public class BaseResource: IDisposable
{
private IntPtr handle;
private Component Components;
private bool disposed = false;
public BaseResource()
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
Components.Dispose();
}
CloseHandle(handle);
handle = IntPtr.Zero;
}
disposed = true;
}
~BaseResource()
{ Dispose(false);
}
public void DoSomething()
{
if(this.disposed)
{
throw new ObjectDisposedException();
}
}
}
public class MyResourceWrapper: BaseResource
{
private ManagedResource addedManaged;
private NativeResource addedNative;
private bool disposed = false;
public MyResourceWrapper()
{
}
protected override void Dispose(bool disposing)
{
if(!this.disposed)
{
try
{
if(disposing)
{
addedManaged.Dispose();
}
CloseHandle(addedNative);
this.disposed = true;
}
finally
{
base.Dispose(disposing);
}
}
}
}
另一个答案的某些方面略有错误,原因有 2 个:
第一
using(NoGateway objNoGateway = new NoGateway())
实际上等同于:
try
{
NoGateway = new NoGateway();
}
finally
{
if(NoGateway != null)
{
NoGateway.Dispose();
}
}
这听起来可能很荒谬,因为“new”运算符不应该返回“null”,除非您有 OutOfMemory 异常。但请考虑以下情况: 1. 调用返回 IDisposable 资源的 FactoryClass 或 2. 如果你有一个类型,根据它的实现,它可能继承也可能不继承 IDisposable - 请记住,我已经在许多客户端上多次看到 IDisposable 模式被错误地实现,其中开发人员只是添加一个 Dispose() 方法而不继承 IDisposable(坏、坏、坏)。你也可能遇到从属性或方法返回 IDisposable 资源的情况(同样是坏的,坏的,坏的 - 不要“放弃你的 IDisposable 资源”
using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
if (NoGateway != null)
{
...
如果“as”运算符返回 null(或返回资源的属性或方法),并且“using”块中的代码防止“null”,则由于“内置”null 检查,在尝试对 null 对象调用 Dispose 时,代码不会爆炸。
您的回复不准确的第二个原因是因为以下标准:
当 GC 摧毁您的对象时,会调用终结器
首先,最终确定(以及 GC 本身)是不确定的。CLR 确定何时调用终结器。即开发人员/代码不知道。如果 IDisposable 模式正确实现(如我上面发布的那样)和 GC.SuppressFinalize() 已被调用,则不会调用 Finalizer。这是正确正确实现模式的重要原因之一。由于每个托管进程只有 1 个 Finalizer 线程,因此无论逻辑处理器的数量如何,您都可以通过备份甚至挂起 Finalizer 线程来忘记调用 GC,从而轻松降低性能。SuppressFinalize() 中。
我已经在我的博客上发布了 Dispose Pattern 的正确实现:How to Properly Implement the Dispose Pattern
评论
NoGateway = new NoGateway();
NoGateway != null
使用 lambda 而不是 IDisposable。
我从来没有对整个 using/IDisposable 的想法感到兴奋。问题在于它要求调用方:
- 知道他们必须使用 IDisposable
- 请记住使用“使用”。
我的新首选方法是使用工厂方法和 lambda
想象一下,我想用 SqlConnection(应该包装在 using 中的东西)做一些事情。经典地,你会这样做
using (Var conn = Factory.MakeConnection())
{
conn.Query(....);
}
新方式
Factory.DoWithConnection((conn)=>
{
conn.Query(...);
}
在第一种情况下,调用方根本无法使用 using 语法。在第二种情况下,用户别无选择。没有创建 SqlConnection 对象的方法,调用方必须调用 DoWithConnection。
DoWithConnection 如下所示
void DoWithConnection(Action<SqlConnection> action)
{
using (var conn = MakeConnection())
{
action(conn);
}
}
MakeConnection
现在是私有的
评论
DoForAll(Action<T>) where T:IComparable<T>
IEnumerable<T>
DoForAll
DoForAll
处置模式:
public abstract class DisposableObject : IDisposable
{
public bool Disposed { get; private set;}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~DisposableObject()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (!Disposed)
{
if (disposing)
{
DisposeManagedResources();
}
DisposeUnmanagedResources();
Disposed = true;
}
}
protected virtual void DisposeManagedResources() { }
protected virtual void DisposeUnmanagedResources() { }
}
继承示例:
public class A : DisposableObject
{
public Component components_a { get; set; }
private IntPtr handle_a;
protected override void DisposeManagedResources()
{
try
{
Console.WriteLine("A_DisposeManagedResources");
components_a.Dispose();
components_a = null;
}
finally
{
base.DisposeManagedResources();
}
}
protected override void DisposeUnmanagedResources()
{
try
{
Console.WriteLine("A_DisposeUnmanagedResources");
CloseHandle(handle_a);
handle_a = IntPtr.Zero;
}
finally
{
base.DisposeUnmanagedResources();
}
}
}
public class B : A
{
public Component components_b { get; set; }
private IntPtr handle_b;
protected override void DisposeManagedResources()
{
try
{
Console.WriteLine("B_DisposeManagedResources");
components_b.Dispose();
components_b = null;
}
finally
{
base.DisposeManagedResources();
}
}
protected override void DisposeUnmanagedResources()
{
try
{
Console.WriteLine("B_DisposeUnmanagedResources");
CloseHandle(handle_b);
handle_b = IntPtr.Zero;
}
finally
{
base.DisposeUnmanagedResources();
}
}
}
上一个:显示提交之间的差异
评论