いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

DevExpress奮闘記-111 (CodeRush Tips&Tricks, Implementing the IDisposable pattern using CodeRush, IDisposableパターン自動実装, 翻訳)

本記事は、DevExpress CodeRush Tips&Tricks (http://www.skorkin.com/)にて公開されている


の拙訳版です。翻訳および公開にあたり、ブログ著者のAlexさんより許可は頂いています。


IDisposableパターンは、ファイル、ストリーム、ハンドルなどのアンマネージドなリソースを
内部で保持しているインスタンスでリソースを閉じたり解放したりする際に利用されます。
リソースのクリーンアップが要求されるクラスに対して、私たちは上記に挙げた標準で明確なパターンである
IDisposableパターンをおすすめします。
このパターンは信頼性、予測可能なクリーンアップ、一時的なリソースリークを防ぐ事を保証するよう
デザインされています。


IDisposableパターンを実行する際にはいくつかの方法が考えられます。
以下のサンプルコードをご覧ください。
http://www.skorkin.com/files/2012/08/ImplementIDisposableSampleCode.png


このクラスが抱える問題は、アンマネージドリソースを利用するStreamReaderとStreamWriterを保持している点です。
このクラスのインスタンスが作成され単純にスコープから外れてしまった場合、オブジェクトはすぐにクリーンアップされず、さらにリソースは不必要にロックされ続けます。


IDisposableパターンを実装するためには、最初に特定のインターフェース(System.IDisposable)を実装する
必要があります。
http://www.skorkin.com/files/2012/08/ImplementIDisposablePatternFirstStep.png


インターフェースには、Disposeメソッドが一つだけ含まれています。
これがPublicなDisposeメソッドとなり実装は以下のようになります。
http://www.skorkin.com/files/2012/08/ImplementIDisposableFirstDisposeMethod.png


2つめのDisposeメソッドを引数付きで呼び出しています。
引数として渡しているtrueはメソッドが手動で呼び出されたことを示しています。
それから、ファイナライザからの実行とそれに関連するパフォーマンス低下を抑止するために
ガベージコレクターの(GCクラス)のSuppressFinalize()メソッドを呼び出しています。
ファイナライズ可能なオブジェクトは、ファイナライゼーションキューに割り当てられ
オブジェクトの生存期間とコストが増すためです。


GC.SupressFinalize()の呼び出しをDispose操作が完全に成功した場合だけに保証するため
Disposeメソッド内の2つの呼び出しの順序は重要です。
DisposeメソッドがDispose(true)で呼び出された際、その呼び出しは失敗し例外が発生するかもしれません。
しかし、後でファイナライザが呼び出された際に今度はDispose(false)で呼び出されます。
Dispose(true)が失敗しても、Dispose(false)はそうでないかもしれないので
これらはコード上の別々の場所で呼ばれます。


2つめのDispose(bool disposing)メソッドは、Dispose処理が手動か自動で呼び出されているかを示す
boolの引数を受け取ります。
手動で呼び出された場合、全てのマネージド、アンマネージドなリソースが解放されます。
GCにより自動で呼び出された場合、マネージドリソースは
既に処理されているので、アンマネージドなリソースのみを解放します。


IDisposableパターンを実装する際、内部で保持している階層全てに対してリソースの解放を
伝播させなければなりません。
ベースクラスにリソースをクリーンアップするためのチャンスを与えるために
呼び出し可能であれば、最後の操作としてベースクラスのDispose(disposing)メソッドを
呼び出します。その際、ベースクラスのメソッドには引数で渡されたdisposingの値と同じ値を渡します。
このように実装したLoggerクラスは以下のようになります。
http://www.skorkin.com/files/2012/08/ImplementIDisposableSecondDisposeMethod.png


アンマネージリソースを保持するオブジェクトの場合、リソースを解放するために
Disposeが呼ばれていない時でも明示的に呼び出されるようファイナライザに追加しなければなりません。
ファイナライザは、Disposeメソッドに対してdisposingをfalseで呼び出します。
falseの値はメソッドが手動で呼ばれていない状態であることを示します:
http://www.skorkin.com/files/2012/08/ImplementIDisposableFinalizer.png


オブジェクトがDisposeされているか否かを示す下記の_Disposedようなフィールドを宣言することを忘れていけません。
http://www.skorkin.com/files/2012/08/ImplementIDisposableFieldDeclaration.png


いつもと同様に、CodeRushは上記のパターンを覚えておかなくてもすむようIDisposableパターンを簡単に実装するための
code providerを提供しています。クラス名の部分でImplement IDisposableコードプロバイダを利用できます:
http://www.skorkin.com/files/2012/08/ImplementIDisposablePreview.png


実行すると、全てのアンマネージドリソースを解放するために必要なコードが生成されます。
する事は、生成コードをどの位置に出力するか指定するだけです:
http://www.skorkin.com/files/2012/08/ImplementIDisposableSelectTarget.png


最終的に以下のようになります:
http://www.skorkin.com/files/2012/08/ImplementIDisposableResult.png


もし、後からDisposeするべきフィールドが増えた場合、Dispose Fieldsコードプロバイダーを利用できます:
http://www.skorkin.com/files/2012/08/DisposeFieldsPreview.png


結果として、Dispose Fieldコードプロバイダは上の図にあるようにPreview Hint(http://www.skorkin.com/2010/09/refactorings-preview-hinting/)
に表示されるDispose用コードを追加します。


Dispose可能なオブジェクトの利用
IDisposableパターンの実装は、Dispose可能なリソースを保持するオブジェクトを作成する際とても重要ですが
このパターンは作成されたクラスのインスタンスをどのように利用するべきかを要求することは出来ません。
残念なことに、開発者に対して使い終わったら明示的にDisposeメソッドを呼ぶよう強制することも、例外が発生した際にDisposeメソッドを確実に呼び出すための適切な例外ハンドリングメカニズムを利用するよう強制することも出来ません。


なので、アンマネージドなリソースにアクセスするオブジェクトを利用する際、usingステートメントを利用してオブジェクトを作成するのが良い方法です。usingステートメントはコードがブロックを抜ける際に自動的にDisposeメソッドを
呼び出します。Introduce Using Statementコードプロバイダ(http://www.skorkin.com/2012/05/refactoring-using-statements-with-coderushrefactor/)を利用する事によってusingステートメントを作成する事が出来ます。
たとえば、以下のコード:
http://www.skorkin.com/files/2012/08/ImplementIDisposableIntroduceUsingStatementSampleCode.png


コードプロバイダは以下のコードを生成します:
http://www.skorkin.com/files/2012/08/ImplementIDisposableIntroduceUsingStatementResult.png


一度クラスがDisposeされたら、そのクラスが保持している全てのリソースは到達不可能であると考えるべきです。
その状態でそのクラスの利用を続行することは不正であるかもしれませんし防ぐべきです。
もしオブジェクトがDisposeされた後アクセスされた場合、ObjectDisposedExceptionにオブジェクトの名称と
エラー内容を提供してスローすることが出来ます。
このチェックは、クラスのpublicメンバ全てに対して行うべきです。


―–
Products: CodeRush Pro
Versions: 12.1 and up
VS IDEs: 2008 and up
Updated: Aug/15/2012
ID: C179


================================
過去の記事については、以下のページからご参照下さい。