在c#中使用Finalize/Dispose方法

C # 2008

我已经在这方面工作了一段时间,但我仍然对在代码中使用finalize和dispose方法感到困惑。我的问题如下:

  1. 我知道我们只需要在处理非托管资源时使用终结器。但是,如果有托管资源调用非托管资源,它还需要实现终结器吗?

  2. 然而,如果我开发了一个不直接或间接使用任何非托管资源的类,我是否应该实现IDisposable以允许该类的客户端使用“using语句”?

    实现IDisposable仅仅是为了让类的客户端使用using语句是否可行?

    using(myClass objClass = new myClass())
    {
    // Do stuff here
    }
    
  3. I have developed this simple code below to demonstrate the Finalize/dispose use:

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

Question about the source code:

  1. Here I have not added the finalizer, and normally the finalizer will be called by the GC, and the finalizer will call the Dispose. As I don't have the finalizer, when do I call the Dispose method? Is it the client of the class that has to call it?

    So my class in the example is called NoGateway and the client could use and dispose of the class like this:

    using(NoGateway objNoGateway = new NoGateway())
    {
    // Do stuff here
    }
    

    当执行到达using块的末尾时,是否会自动调用Dispose方法,还是客户端必须手动调用Dispose方法?即。

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
    
  2. I am using the WebClient class in my NoGateway class. Because WebClient implements the IDisposable interface, does this mean that WebClient indirectly uses unmanaged resources? Is there a hard and fast rule to follow this? How do I know that a class uses unmanaged resources?

202702 次浏览
using(NoGateway objNoGateway = new NoGateway())

等于

try
{
NoGateway = new NoGateway();
}


finally
{
NoGateway.Dispose();
}

在GC销毁对象时调用终结器。这可能与你离开方法时完全不同。Dispose of IDisposable在你离开using块后立即被调用。因此,这种模式通常是在您不再需要资源后立即使用using来释放它们。

1) WebClient是一个托管类型,所以你不需要终结器。如果您的用户不Dispose()您的NoGateway类,并且本机类型(不被GC收集)之后需要清理,则需要结束器。在这种情况下,如果用户没有调用Dispose(),所包含的WebClient将在NoGateway调用之后被GC丢弃。

间接地,是的,但你不必担心。你的代码是正确的,你不能防止你的用户很容易忘记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);
//}
}

注意,我没有在B中声明终结器;只有在有实际的非托管资源要处理时,才应该实现终结器。CLR处理可终结对象与处理不可终结对象不同,即使调用了SuppressFinalize

因此,除非必须,否则你不应该声明终结器,但你可以给类的继承程序一个钩子来调用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);
}
}

如果你不直接使用非托管资源(SafeHandle和friends不算数,因为它们声明了自己的终结器),那么不要实现终结器,因为GC以不同的方式处理可终结类,即使你后来抑制了终结器。还要注意,即使B没有终结器,它仍然调用SuppressFinalize来正确处理任何实现了终结器的子类。

当一个类实现IDisposable接口时,这意味着在某个地方有一些非托管资源,当您使用完该类时,这些资源应该被删除。实际资源封装在类中;您不需要显式地删除它们。只需调用Dispose()或将类包装在using(...) {}中,就可以确保在必要时摆脱任何非托管资源。

  1. 如果您正在使用使用非托管资源的其他托管对象,则您没有责任确保这些托管对象已完成。你的责任是当Dispose在你的对象上被调用时,在那些对象上调用Dispose,它在那里停止。

  2. 如果你的类不使用任何稀缺资源,我不明白你为什么要让你的类实现IDisposable。只有在以下情况下你才应该这样做:

    • 知道你的对象中很快就会有稀缺资源,但不是现在(我的意思是“我们还在开发,在我们完成之前它会在这里”,而不是“我认为我们会需要这个”)
    • 利用稀缺资源
    • 李< / ul > < / >
    • 是的,使用您的代码的代码必须调用对象的Dispose方法。是的,使用你的对象的代码可以使用using,如你所示。

    • (2)WebClient很可能使用非托管资源,或者其他实现IDisposable的托管资源。然而,确切的原因并不重要。重要的是,它实现了IDisposable,因此,当你完成它时,它就落在你身上,通过处理对象来采取行动,即使它最终证明WebClient根本不使用其他资源。

据我所知,强烈建议不要使用终结器/析构函数:

public ~MyClass() {
//dont use this
}

大多数情况下,这是因为不知道何时或是否会调用它。处置方法是更好的,特别是如果你使用或直接处置。

使用是好的。使用它:)

注意,任何IDisposable实现都应该遵循以下模式(IMHO)。我根据几个优秀的。net“神”.NET框架设计指南的信息开发了这个模式(注意,由于某些原因MSDN没有遵循这个模式!).NET框架设计指南是由Krzysztof Cwalina(当时的CLR架构师)、Brad Abrams(我相信当时的CLR程序经理)和Bill Wagner ([Effective c#]和[More Effective c#])(在亚马逊网站上看看就知道了:

注意,除非你的类直接包含(而不是继承)非托管资源,否则永远不要实现Finalizer。一旦在类中实现了Finalizer,即使它从未被调用,也保证它会在一个额外的集合中存在。它被自动放置在终结队列(在单个线程上运行)上。还有一个非常重要的提示……在终结器中执行的所有代码(如果你需要实现一个)必须是线程安全的和异常安全的!否则,不好的事情就会发生……不确定的行为,在异常的情况下,致命的不可恢复的应用程序崩溃)。

我把模式放在一起(并为其编写了代码片段)如下:

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

我已经在我的博客上发布了这个实现:如何正确实现Dispose模式

没有人回答你是否应该实现IDisposable的问题,即使你不需要它。

简单的回答:不

长一点的回答:

这将允许你的类的消费者使用“using”。我想问的问题是,他们为什么要这么做?大多数开发者不会使用“使用”,除非他们知道他们必须这么做——他们是怎么知道的。要么

  • 很明显,从经验来看(例如,一个套接字类)
  • 它的记录
  • 他们很谨慎,可以看到类实现了IDisposable

因此,通过实现IDisposable,你可以告诉开发人员(至少是一些开发人员)这个类封装了一些必须发布的东西。他们会使用“using”——但在其他情况下,使用是不可能的(对象的作用域不是本地的);他们将不得不开始担心在其他情况下对象的生命周期-我肯定会担心。但这是不必要的

你实现Idisposable,让他们使用使用,但他们不会使用使用,除非你告诉他们。

所以不要这么做

我同意与pm100(并且应该在我之前的帖子中明确地说这一点)。

除非需要,否则永远不要在类中实现IDisposable。具体来说,大约有5次你需要/应该实现IDisposable:

  1. 你的类显式包含(即不是通过继承)任何实现IDisposable的托管资源,一旦你的类不再使用,就应该清理。例如,如果你的类包含一个Stream、DbCommand、DataTable等的实例。

  2. 你的类显式地包含了任何实现Close()方法的托管资源——例如IDataReader, IDbConnection,等等。注意,其中一些类通过Dispose()和Close()方法实现了IDisposable。

  3. 你的类显式包含一个非托管资源-例如COM对象,指针(是的,你可以在托管c#中使用指针,但它们必须在“不安全”块中声明,等等。 对于非托管资源,还应该确保在RCW上调用System.Runtime.InteropServices.Marshal.ReleaseComObject()。尽管RCW在理论上是一个托管的包装器,但在封面下仍然存在引用计数
  4. 如果您的类使用强引用订阅事件。您需要注销/脱离事件。在尝试取消注册/分离它们之前,总是要确保这些不是空的!

  5. 你的类包含以上的任意组合…

对于使用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
}


}

来自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);
}
}
}
}

另一个答案的某些方面有点不正确,原因有二:

首先,

using(NoGateway objNoGateway = new NoGateway())

Actually相当于:

try
{
NoGateway = new NoGateway();
}
finally
{
if(NoGateway != null)
{
NoGateway.Dispose();
}
}
这可能听起来很荒谬,因为'new'操作符永远不应该返回'null',除非你有一个OutOfMemory异常。但考虑以下情况: 1. 调用一个返回IDisposable资源的FactoryClass或 2. 如果你有一个可能继承IDisposable,也可能不继承IDisposable的类型,这取决于它的实现——记住,我曾经在许多客户端上看到过IDisposable模式被错误地实现过很多次,开发人员只是添加了一个Dispose()方法,而没有继承IDisposable(糟糕,糟糕,糟糕)。你也可以有从属性或方法返回IDisposable资源的情况(同样糟糕,糟糕,糟糕-不要'放弃你的IDisposable资源)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
if (NoGateway != null)
{
...

如果'as'操作符返回null(或返回资源的属性或方法),并且'using'块中的代码可以防止'null',那么在尝试对空对象调用Dispose时,您的代码不会因为'内置'空检查而崩溃。

你的回复不准确的第二个原因是由于以下stmt:

在GC销毁对象时调用终结器

首先,终结(以及GC本身)是不确定的。CLR决定何时调用终结器。也就是说,开发人员/代码不知道。如果IDisposable模式被正确实现(如上所述)并且GC.SuppressFinalize()已经被调用,Finalizer将不会被调用。这是正确实现模式的重要原因之一。由于每个托管进程只有1个Finalizer线程,无论逻辑处理器的数量如何,您可以通过忘记调用GC.SuppressFinalize()来备份甚至挂起Finalizer线程,从而很容易降低性能。

我已经在我的博客上发布了Dispose Pattern的正确实现:如何正确实现Dispose模式

使用lambdas代替IDisposable。

我从来没有对使用/IDisposable这个想法感到兴奋。问题是它要求调用者:

  • 知道他们必须使用IDisposable
  • 记住使用“using”。

我新的首选方法是使用工厂方法和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现在是私有的

处理模式:

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