簡體   English   中英

C#中Finalize/Dispose方法的使用

[英]Use of Finalize/Dispose method in C#

C# 2008

我已經研究了一段時間了,我仍然對在代碼中使用 finalize 和 dispose 方法感到困惑。 我的問題如下:

  1. 我知道在處理非托管資源時我們只需要一個終結器。 但是,如果存在調用非托管資源的托管資源,它是否仍然需要實現終結器?

  2. 但是,如果我開發一個不直接或間接使用任何非托管資源的類,我是否應該實現IDisposable以允許該類的客戶端使用“using 語句”?

    實現 IDisposable 只是為了讓您的類的客戶能夠使用 using 語句是否可行?

     using(myClass objClass = new myClass()) { // Do stuff here }
  3. 我在下面開發了這個簡單的代碼來演示 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); } }

關於源代碼的問題:

  1. 這里我沒有添加終結器,一般情況下終結器會被GC調用,終結器會調用Dispose。 由於我沒有終結器,我什么時候調用 Dispose 方法? 是類的客戶端必須調用它嗎?

    因此,我在示例中的類稱為 NoGateway,客戶端可以像這樣使用和處置該類:

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

    當執行到 using 塊的末尾時會自動調用 Dispose 方法,還是客戶端必須手動調用 dispose 方法? IE

     NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
  2. 我在我的NoGateway類中使用WebClient類。 因為WebClient實現了IDisposable接口,這是否意味着WebClient間接使用了非托管資源? 是否有嚴格的規則來遵循這一點? 我怎么知道一個類使用了非托管資源?

推薦的 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聲明終結器; 如果您有實際的非托管資源要處理,您應該只實現終結器。 即使調用了SuppressFinalize ,CLR 處理可終結對象的方式也不同於不可終結對象。

因此,除非必須,否則不應聲明終結器,但是如果類的繼承者直接使用非托管資源,則為它們提供一個鈎子以調用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和朋友不計算在內,因為它們聲明了自己的終結器),則不要實現終結器,因為 GC 處理可終結類的方式不同,即使您稍后取消了終結器. 另請注意,即使B沒有終結器,它仍會調用SuppressFinalize以正確處理任何實現終結器的子類。

當一個類實現 IDisposable 接口時,這意味着在您使用完該類后,應該刪除某些非托管資源。 實際資源被封裝在類中; 您不需要明確刪除它們。 只需調用Dispose()或將類包裝在using(...) {}確保根據需要刪除任何非托管資源。

實現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
  }

}

請注意,任何 IDisposable 實現都應遵循以下模式(恕我直言)。 我根據幾個優秀的 .NET“神”的信息開發了這個模式.NET 框架設計指南(注意 MSDN 由於某種原因沒有遵循這個!)。 .NET Framework 設計指南是由 Krzysztof Cwalina(當時的 CLR 架構師)和 Brad Abrams(我相信當時的 CLR 程序經理)和 Bill Wagner([Effective C#] 和 [More Effective C#](只需看看在 Amazon.com 上查找這些:

請注意,除非您的類直接包含(而不是繼承)非托管資源,否則您永遠不應該實現終結器。 一旦你在一個類中實現了一個 Finalizer,即使它從未被調用過,它也可以保證為一個額外的集合而存在。 它會自動放置在 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 );
    }
}

我已經在我的博客上發布了這個實現: 如何正確實現處置模式

我同意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 上的一些關於為什么這是一個壞主意的文章。 我也在這里發布了它 - 不要在 WCF 代理中使用“using()”

使用 lambdas 而不是 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現在是私有的

沒有人回答關於是否應該實施 IDisposable 的問題,即使您不需要它。

簡短回答:否

長答案:

這將允許您班級的消費者使用“使用”。 我想問的問題是——他們為什么要這樣做? 大多數開發人員不會使用“使用”,除非他們知道必須使用 - 以及他們是如何知道的。 任何一個

  • 從經驗中可以看出它們(例如套接字類)
  • 其有據可查
  • 他們很謹慎,可以看到該類實現了 IDisposable

因此,通過實現 IDisposable,您是在告訴開發人員(至少一些)這個類包含了一些必須發布的東西。 他們將使用 'using' - 但在其他情況下使用是不可能的(對象的范圍不是本地的); 在其他情況下,他們將不得不開始擔心對象的生命周期——我肯定會擔心。 但這不是必須的

您實施 Idisposable 以使他們能夠使用 using,但除非您告訴他們,否則他們不會使用 using。

所以不要這樣做

配置模式:

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

由於兩個原因,另一個答案的某些方面略有不正確:

第一的,

using(NoGateway objNoGateway = new NoGateway())

實際上相當於:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

這聽起來可能很荒謬,因為除非出現 OutOfMemory 異常,否則 'new' 運算符永遠不應該返回 'null'。 但請考慮以下情況: 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 對象調用 Dispose 時,您的代碼不會因為以下原因而崩潰“內置”空檢查。

你的回復不准確的第二個原因是因為下面的stmt:

在 GC 銷毀您的對象時調用終結器

首先,Finalization(以及 GC 本身)是不確定的。 CLR 確定何時調用終結器。 即開發人員/代碼不知道。 如果正確實現了 IDisposable 模式(正如我在上面發布的那樣)並且 GC.SuppressFinalize() 已被調用,則不會調用終結器。 這是正確實施模式的重要原因之一。 由於每個托管進程只有 1 個 Finalizer 線程,無論邏輯處理器的數量如何,您都可以通過備份甚至通過忘記調用 GC.SuppressFinalize() 掛起 Finalizer 線程來輕松降低性能。

我在我的博客上發布了一個正確的處置模式實現如何正確實現處置模式

  1. 如果您正在使用使用非托管資源的其他托管對象,則您沒有責任確保這些資源已完成。 您的責任是在對您的對象調用 Dispose 時對這些對象調用 Dispose,並在那里停止。

  2. 如果您的班級不使用任何稀缺資源,我不明白您為什么要讓您的班級實現 IDisposable。 您應該只在以下情況下這樣做:

    • 知道你的對象很快就會有稀缺資源,只是不是現在(我的意思是“我們仍在開發,它會在我們完成之前就在這里”,而不是“我認為我們需要這個”)
    • 使用稀缺資源
  3. 是的,使用您的代碼的代碼必須調用您的對象的 Dispose 方法。 是的,使用您的對象的代碼可以如您所示使用using

  4. (又是 2 次?)WebClient 很可能使用非托管資源或其他實現 IDisposable 的托管資源。 然而,確切的原因並不重要。 重要的是它實現了 IDisposable,因此當您處理完對象時,您需要根據該知識采取行動,即使事實證明 WebClient 根本不使用其他資源。

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

1) WebClient 是托管類型,因此您不需要終結器。 如果您的用戶不使用您的 NoGateway 類的 Dispose() 並且需要在之后清理本機類型(未由 GC 收集),則需要終結器。 在這種情況下,如果用戶不調用 Dispose(),則包含的 WebClient 將在 NoGateway 調用后立即由 GC 處理。

2)間接是的,但您不必擔心。 你的代碼是正確的,你不能防止你的用戶很容易忘記 Dispose()。

using(NoGateway objNoGateway = new NoGateway())

相當於

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

在 GC 銷毀您的對象時調用終結器。 這可能與您離開方法的時間完全不同。 在您離開 using 塊后立即調用 IDisposable 的 Dispose。 因此,該模式通常用於在您不再需要資源后立即使用 using 來釋放資源。

據我所知,強烈建議不要使用終結器/析構函數:

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

大多數情況下,這是由於不知道何時或是否會調用它。 dispose 方法要好得多,特別是如果您直接使用或 dispose。

使用是好的。 用它 :)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM