406

C#2008

私はこれにしばらく取り組んできましたが、コードでのfinalizeメソッドとdisposeメソッドの使用についてはまだ混乱しています。私の質問は以下の通りです:

  1. 管理されていないリソースを処分するときに必要なのはファイナライザーだけであることを私は知っています。ただし、管理されていないリソースを呼び出す管理されたリソースがある場合でも、ファイナライザーを実装する必要がありますか?

  2. ただし、管理されていないリソースを直接または間接的に使用しないクラスを開発する場合、IDisposableそのクラスのクライアントが「usingステートメント」を使用できるようにを実装する必要がありますか?

    クラスのクライアントがusingステートメントを使用できるようにするためだけにIDisposableを実装することは可能でしょうか?

    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メソッドを手動で呼び出す必要がありますか?すなわち

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
    
  2. WebClientクラスでクラスを使用していNoGatewayます。インターフェイスをWebClient実装しているため、これは間接的に管理されていないリソースを使用することを意味しますか?これに従うための厳格なルールはありますか?クラスが管理されていないリソースを使用していることをどのように知ることができますか?IDisposableWebClient

4

13 に答える 13

444

推奨されるIDisposableパターンはこちらです。IDisposableを使用するクラスをプログラミングする場合、通常は次の2つのパターンを使用する必要があります。

アンマネージリソースを使用しない封印されたクラスを実装する場合は、通常のインターフェイス実装と同様に、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。廃棄する実際の管理されていないリソースがある場合にのみ、ファイナライザーを実装する必要があります。SuppressFinalizeCLRは、が呼び出された場合でも、ファイナライズ可能なオブジェクトをファイナライズ不可能なオブジェクトとは異なる方法で処理します。

したがって、必要がない限りファイナライザーを宣言するべきではありませんが、クラスの継承者に、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(...) {}管理されていないリソースが必要に応じて削除されます。

于 2009-05-22T16:52:59.980 に答える
126

実装する公式パターン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
  }

}
于 2010-01-29T17:19:14.513 に答える
39

IDisposable の実装はすべて、以下のパターン (IMHO) に従う必要があることに注意してください。このパターンは、いくつかの優れた .NET の「神々」である.NET Framework Design Guidelinesからの情報に基づいて開発しました(MSDN は何らかの理由でこれに従っていないことに注意してください!)。.NET Framework 設計ガイドラインは、Krzysztof Cwalina (当時の CLR アーキテクト) と Brad Abrams (当時の CLR プログラム マネージャーだったと思います) と Bill Wagner ([効果的な C#] と [より効果的な C#] (簡単なAmazon.com でこれらを探します。

クラスにアンマネージ リソースが直接含まれている (継承されていない) 場合を除き、Finalizer を実装しないでください。クラスに Finalizer を実装すると、それが呼び出されなくても、余分なコレクションのために存在することが保証されます。ファイナライズ キュー (シングル スレッドで実行) に自動的に配置されます。また、非常に重要な注意事項が 1 つあります。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 パターンを適切に実装する方法

于 2010-01-13T21:40:07.420 に答える
23

私は pm100に同意します(以前の投稿でこれを明確に述べるべきでした)。

必要でない限り、IDisposable をクラスに実装しないでください。非常に具体的に言うと、IDisposable を実装する必要がある/実装する必要があるのは約 5 回です。

  1. クラスには、IDisposable を実装するマネージ リソースが明示的に (つまり、継承によってではなく) 含まれており、クラスが使用されなくなったらクリーンアップする必要があります。たとえば、クラスに Stream、DbCommand、DataTable などのインスタンスが含まれているとします。

  2. クラスには、Close() メソッドを実装するマネージ リソース (IDataReader、IDbConnection など) が明示的に含まれます。これらのクラスの一部は、Dispose() メソッドと Close() メソッドを持つことによって IDisposable を実装することに注意してください。

  3. クラスには明示的にアンマネージ リソース (COM オブジェクト、ポインターなど) が含まれています (はい、マネージ C# でポインターを使用できますが、「安全でない」ブロックなどで宣言する必要があります。アンマネージ リソースの場合は、次のことも確認する必要があります。 RCW で System.Runtime.InteropServices.Marshal.ReleaseComObject() を呼び出します。RCW は理論的にはマネージ ラッパーですが、内部ではまだ参照カウントが行われています。

  4. クラスが強い参照を使用してイベントをサブスクライブする場合。イベントから自分自身を登録解除/切り離す必要があります。それらを登録解除/デタッチしようとする前に、常にこれらがnullでないことを確認してください!.

  5. あなたのクラスには、上記の任意の組み合わせが含まれています...

COM オブジェクトを操作して Marshal.ReleaseComObject() を使用する代わりに、System.Runtime.InteropServices.SafeHandle クラスを使用することをお勧めします。

BCL (Base Class Library Team) には、これに関する優れたブログ投稿がありますhttp://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

非常に重要な注意事項の 1 つは、WCF を使用してリソースをクリーンアップしている場合は、'using' ブロックを常に回避する必要があるということです。これが悪い考えである理由については、たくさんのブログ投稿があり、MSDN にもいくつかあります。私もそれについてここに投稿しました - WCFプロキシで「using()」を使用しないでください

于 2010-01-14T00:25:15.513 に答える
13

IDisposable の代わりにラムダを使用します。

using/IDisposable のアイデア全体にわくわくしたことはありません。問題は、呼び出し元に次のことを要求することです。

  • IDisposable を使用する必要があることを知っている
  • 「使用する」を使用することを忘れないでください。

私の新しい推奨方法は、代わりにファクトリメソッドとラムダを使用することです

SqlConnection (using でラップする必要があるもの) で何かをしたいとします。古典的にはあなたがするだろう

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

新しい方法

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

最初のケースでは、呼び出し元は using 構文を使用できませんでした。2 番目のケースでは、ユーザーには選択の余地がありません。SqlConnection オブジェクトを作成するメソッドはありません。呼び出し元は DoWithConnection を呼び出す必要があります。

DoWithConnection は次のようになります

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection現在は非公開です

于 2011-07-20T16:25:06.647 に答える
12

IDisposable を実装する必要があるかどうかについての質問には誰も答えませんでした。

簡単な答え : いいえ

長い答え:

これにより、クラスの消費者が「using」を使用できるようになります。私が尋ねる質問は、なぜ彼らはそれをするのですか? ほとんどの開発者は、そうしなければならないことを知っていない限り、「using」を使用しません。また

  • それらは経験から明らかです(たとえば、ソケットクラス)
  • その文書化
  • 彼らは用心深く、クラスが IDisposable を実装していることを確認できます

したがって、IDisposable を実装することで、開発者 (少なくとも一部) に、このクラスがリリースする必要があるものをラップすることを伝えています。それらは「using」を使用しますが、使用できない場合もあります (オブジェクトのスコープはローカルではありません)。他のケースでは、オブジェクトの寿命について心配し始める必要があります-私は確かに心配します. しかし、これは必要ありません

Idisposable を実装して using を使用できるようにしますが、指示しない限り using を使用しません。

だからやらないで

于 2010-01-13T21:53:49.110 に答える
5

処分パターン:

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();
        }
    }
}
于 2014-08-28T10:57:14.353 に答える
4
  1. 管理されていないリソースを使用している他の管理対象オブジェクトを使用している場合、それらがファイナライズされていることを確認するのはユーザーの責任ではありません。あなたの責任は、オブジェクトに対してDisposeが呼び出されたときに、それらのオブジェクトに対してDisposeを呼び出すことであり、そこで停止します。

  2. クラスが不足しているリソースを使用していない場合、クラスにIDisposableを実装させる理由がわかりません。次の場合にのみそうする必要があります。

    • 今ではなく、すぐにオブジェクトにリソースが不足することを知ってください(つまり、「これが必要になると思う」ではなく、「まだ開発中です。完了する前にここにあります」という意味です。 ")
    • 希少な資源を使う
  3. はい、コードを使用するコードは、オブジェクトのDisposeメソッドを呼び出す必要があります。そして、はい、あなたのオブジェクトを使用するコードはusingあなたが示したように使用することができます。

  4. (2つも?)WebClientは、アンマネージリソース、またはIDisposableを実装する他のマネージリソースのいずれかを使用している可能性があります。ただし、正確な理由は重要ではありません。重要なのは、IDisposableを実装していることです。したがって、WebClientが他のリソースをまったく使用していないことが判明した場合でも、オブジェクトを使い終わったら、オブジェクトを破棄してその知識に基づいて行動する必要があります。

于 2009-05-22T16:55:34.987 に答える
4

別の回答のいくつかの側面は、2つの理由でわずかに正しくありません。

初め、

using(NoGateway objNoGateway = new NoGateway())

実際には次と同等です:

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

OutOfMemory例外がない限り、「new」演算子は「null」を返さないため、これはばかげているように聞こえるかもしれません。ただし、次の場合を考慮してください。1.IDisposableリソースを返すFactoryClassを呼び出す。多くのクライアントでは、開発者がIDisposable(bad、bad、bad)から継承せずにDispose()メソッドを追加するだけです。IDisposableリソースがプロパティまたはメソッドから返される場合もあります(ここでも、bad、bad、bad-IDisposableリソースを提供しないでください)

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

'as'演算子がnull(またはリソースを返すプロパティまたはメソッド)を返し、'using'ブロックのコードが'null'から保護されている場合、nullオブジェクトでDisposeを呼び出そうとしても、コードは爆発しません。 '組み込み'nullチェック。

返信が正確でない2つ目の理由は、次のstmtが原因です。

GCがオブジェクトを破棄すると、ファイナライザーが呼び出されます

まず、ファイナライズ(およびGC自体)は非決定的です。CLRは、ファイナライザーを呼び出すタイミングを決定します。つまり、開発者/コードにはわかりません。IDisposableパターンが正しく実装され(上記で投稿したように)、GC.SuppressFinalize()が呼び出された場合、ファイナライザーは呼び出されません。これは、パターンを正しく実装する大きな理由の1つです。論理プロセッサの数に関係なく、管理対象プロセスごとにファイナライザスレッドは1つしかないため、GC.SuppressFinalize()の呼び出しを忘れて、ファイナライザスレッドをバックアップしたり、ハングさせたりすると、パフォーマンスが簡単に低下する可能性があります。

ブログにDisposePatternの正しい実装を投稿しました:DisposePatternを適切に実装する方法

于 2011-03-09T14:48:51.353 に答える
2

1)WebClientはマネージドタイプであるため、ファイナライザーは必要ありません。ユーザーがNoGatewayクラスのDispose()を使用せず、ネイティブタイプ(GCによって収集されない)を後でクリーンアップする必要がある場合は、ファイナライザーが必要です。この場合、ユーザーがDispose()を呼び出さないと、含まれているWebClientは、NoGatewayが呼び出した直後にGCによって破棄されます。

2)間接的にはい、しかしあなたはそれについて心配する必要はないはずです。コードは現状のままで正しく、ユーザーがDispose()を簡単に忘れないようにすることはできません。

于 2009-05-22T16:50:58.203 に答える
2

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);
         }
      }
   }
}
于 2010-03-28T09:52:50.790 に答える
1
using(NoGateway objNoGateway = new NoGateway())

と同等です

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

GCがオブジェクトを破棄すると、ファイナライザーが呼び出されます。これは、メソッドを終了するときとはまったく異なる時間になる可能性があります。IDisposableの破棄は、usingブロックを離れた直後に呼び出されます。したがって、パターンは通常、リソースが不要になった直後に、を使用してリソースを解放するために使用されます。

于 2009-05-22T16:50:11.437 に答える
-5

私の知る限り、ファイナライザ/デストラクタを使用しないことを強くお勧めします。

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

ほとんどの場合、これはいつ呼び出されるか、または呼び出されるかどうかがわからないためです。特に、直接使用または破棄する場合は、破棄方法の方がはるかに優れています。

使用は良いです。これを使って :)

于 2009-05-22T16:56:53.360 に答える