web-dev-qa-db-ja.com

C#でパターンを完成/処理する

C#2008

私はしばらくこれに取り組んでいます、そして私はまだいくつかの問題について混乱しています。私の質問は以下の通りです

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

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

    クラスのクライアントがusingステートメントを使用できるようにIDisposableを実装することは許容できるでしょうか。

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
    
  3. 私は以下のこの単純なコードを開発して、ファイナライズ/破棄パターンを実演しました。

    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. NoGatewayクラスでwebclientクラスを使用しています。 WebクライアントはIDisposableインターフェイスを実装しているため、Webクライアントは管理されていないリソースを間接的に使用しているということでしょうか。これについて従うべき厳しく速い規則はありますか?クラスがアンマネージリソースを使用していることをどうやって知ることができますか?

359
ant2009

推奨される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でファイナライザーを宣言していないことに注意してください。あなたが処分するための実際の管理されていないリソースがある場合にのみファイナライザを実装するべきです。たとえ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(...) {}でラップすると、管理されていないリソースが必要に応じて削除されます。

397
thecoop

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
  }

}
116
Jordão

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

クラスに直接アンマネージリソースが含まれていない場合は、決してファイナライザを実装しないでください。ファイナライザをクラスに実装した後は、たとえ呼び出されなくても、追加のコレクションに対応することが保証されています。自動的にファイナライズキュー(シングルスレッドで実行されます)に配置されます。また、非常に重要な注意点が1つあります。ファイナライザ内で実行されるすべてのコード(実装する必要がある場合)は、スレッドセーフおよび例外セーフでなければなりません。そうでなければ悪いことが起こります...(すなわち、未定義の振る舞いと例外の場合には致命的な回復不可能なアプリケーションクラッシュ)。

私がまとめた(そしてコードスニペットを書いた)パターンは次のとおりです。

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

私はこの実装を私のブログに投稿しました。 破棄パターンを正しく実装する方法

36
Dave Black

私は同意します pm100と (そして私の以前の投稿で明示的にこれを言ったはずです)。

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

  1. あなたのクラスはIDisposableを実装していて、あなたのクラスがもう使われなくなったら一掃されるべきである管理されたリソースを明示的に(すなわち継承を通してではなく)含んでいます。たとえば、クラスにStream、DbCommand、DataTableなどのインスタンスが含まれている場合などです。

  2. あなたのクラスは、Close()メソッドを実装する管理リソースを明示的に含んでいます。 IDataReader、IDbConnectionなど。これらのクラスの中には、Dispose()とClose()メソッドを使用してIDisposableを実装しているものがあります。

  3. あなたのクラスは管理されていないリソースを明示的に含んでいます - 例えばCOMオブジェクト、ポインタ(はい、マネージC#ではポインタを使用できますが、安全でないブロックなどで宣言する必要があります。アンマネージリソースの場合は、System.Runtime.InteropServices.Marshalも必ず呼び出す必要があります。 RCW上の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

WCFを使用してリソースをクリーンアップする場合は、必ず 'using'ブロックを避けるようにしてください。たくさんのブログ記事があり、それがなぜ悪い考えなのかについてのMSDNへの投稿もあります。私はそれについてもここに投稿しました - WCFプロキシで 'using()'を使用しないでください

22
Dave Black

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

私は/ IDisposableアイデアを使って全体に興奮したことがない。問題は、呼び出し側に次のことを要求することです。

  • 彼らはIDisposableを使用する必要があることを知っている
  • 忘れずに 'using'を使ってください。

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

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は非公開になりました

12
pm100

あなたがそれを必要としないとしても、誰もあなたがIDisposableを実装すべきかどうかについての質問に答えませんでした。

短い答え:いいえ

長い答え:

これはあなたのクラスの消費者が 'using'を使うことを可能にするでしょう。私が尋ねる質問は - なぜ彼らはそれをするのですか?彼らがしなければならないと彼らが知っていない限り、ほとんどの開発者は 'using'を使用しないでしょう。どちらでも

  • 経験からそれらを理解すること(例えばソケットクラス)
  • その文書化
  • これらは用心深く、そのクラスがIDisposableを実装していることがわかります。

そのため、IDisposableを実装することで、このクラスがリリースする必要があるものをまとめていることを開発者(少なくとも一部)に伝えています。それらは 'using'を使用します - しかしusingが不可能な場合もあります(objectのスコープはローカルではありません)。それ以外の場合、それらはオブジェクトの寿命について心配し始めなければならないでしょう - 私は確かに心配するでしょう。しかし、これは必要ではありません

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

だからそれをしてはいけない

10
pm100
  1. 管理されていないリソースを使用している他の管理対象オブジェクトを使用している場合は、それらを確実に確定することはあなたの責任ではありません。あなたの責任は、Disposeがあなたのオブジェクトに対して呼ばれたときにそれらのオブジェクトに対してDisposeを呼び出すことであり、そこで停止します。

  2. あなたのクラスが少しのリソースも使用しないならば、私はあなたがあなたのクラスにIDisposableを実装させる理由を見逃しています。あなたがそうであるならば、あなたはそうするべきです:

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

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

別の答え のいくつかの側面は、2つの理由でわずかに間違っています。

最初、

using(NoGateway objNoGateway = new NoGateway())

実際には以下と同等です。

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

OutOfMemory例外がない限り、 'new'演算子は決して 'null'を返さないので、これはばかげて聞こえるかもしれません。 1. IDisposableリソースを返すFactoryClassを呼び出すか、または2.実装に応じてIDisposableから継承される可能性があるタイプまたは継承されないタイプがある場合 - IDisposableパターンが正しく実装されていないことを多く覚えています。開発者がIDisposableから継承せずにDispose()メソッドを追加するだけの(悪い、悪い、悪い)多くのクライアントでは、このような状況に陥ります。また、IDisposableリソースがプロパティまたはメソッドから返されることもあります(これも悪い、悪い、悪いです - IDisposableリソースを譲渡しないでください)。

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

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

あなたの返事が正確ではない第二の理由は、以下の理由による。

ファイナライザはあなたのオブジェクトを破壊するGCに呼ばれます

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

私は自分のブログにDispose Patternの正しい実装を投稿しました。 Dispose Patternを正しく実装する方法

4
Dave Black

パターンを配置する:

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();
        }
    }
}
3
using(NoGateway objNoGateway = new NoGateway())

と同等です

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

ファイナライザはあなたのオブジェクトを破壊するGCに呼ばれます。これはあなたがあなたの方法を去るときとは全く異なる時にあり得る。 usingブロックを離れた直後に、Dispose of IDisposableが呼び出されます。したがって、パターンは通常、リソースが不要になった直後にusingリソースを解放するためにusingを使用することです。

2
Daniel Fabian

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

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

2
Jesse C. Slicer

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
devnull