So-net無料ブログ作成
検索選択

IDisposable [Programming]

.NET Framework の環境で開発をしていると結構な割合で出てくるこのインターフェース。公式な文書以外にも Tips として情報が公開されていることからも分かる通り、結構ややっこしいインターフェースだったりする。IDisposable インターフェースの定義そのものは下記の通り。

Namespace System
'
' 概要:
' アンマネージ リソースを解放するためのメカニズムを提供します。

Public Interface IDisposable
'
' 概要:
' アンマネージ リソースの解放およびリセットに関連付けられているアプリケーション定義のタスクを実行します。
Sub Dispose()
End Interface
End Namespace

IDisposable が解りづらい最大の要因は、これがインターフェースだということにある。インターフェースは見かけを指定することはできるけど、実装を強制することができないから。単に IDisposable インターフェースの要件を満たす実装をするだけならコレでもいい。

Class Test
Implements IDisposable

Public Sub Dispose() Implements IDisposable.Dispose
End Sub
End Class 

ただ、コレではなんの意味もないということは考えないでも解るだろう。IDisposable の使用目的(アンマネージ リソースを解放するためのメカニズムを提供します)に合わせた実装にするには多少手を加える必要があるのだけれど、ここでは先人の知恵を拝借するのが間違いない。Visual Studio の吐くコードはこんな感じになる。

Class Test
Implements IDisposable

Private disposedValue As Boolean ' 重複する呼び出しを検出するには

' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
' TODO: マネージ状態を破棄します (マネージ オブジェクト)。
End If

' TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下の Finalize() をオーバーライドします。
' TODO: 大きなフィールドを null に設定します。
End If
disposedValue = True
End Sub

' TODO: 上の Dispose(disposing As Boolean) にアンマネージ リソースを解放するコードが含まれる場合にのみ Finalize() をオーバーライドします。
'Protected Overrides Sub Finalize()
' ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(disposing As Boolean) に記述します。
' Dispose(False)
' MyBase.Finalize()
'End Sub

' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
Public Sub Dispose() Implements IDisposable.Dispose
' このコードを変更しないでください。クリーンアップ コードを上の Dispose(disposing As Boolean) に記述します。
Dispose(True)
' TODO: 上の Finalize() がオーバーライドされている場合は、次の行のコメントを解除してください。
' GC.SuppressFinalize(Me)
End Sub
End Class

Visual Basic では、IDisposable インターフェースを実装を定義(Implements IDisposable と記述し Enter キーを押下)した瞬間に必要なフィールド及びメソッドがすべて展開されるので、あとは TODO に必要な処理を追加してやればいい。コメントに今一つ意味不明な単語があるけど、とりあえず横に置いておいて、多くの場合ハイライトした部分だけが必須で、それ以外はほぼ不要ということで問題ないはず。

.NET Framework においては、ガベージコレクションの対象とならないオブジェクトをアンマネージド リソースと呼ぶ。メモリ以外のオブジェクトを指すと考えて問題ない。ファイルを扱う際のファイルハンドルやデータベースのコネクションなどがこれに含まれる。

注意しなければならないのは、上記のソースコードに含まれる、マネージ オブジェクトやアンマネージ オブジェクトという単語はコレとは少し違うということ。

例えば、System.IO.FileStream というクラスがある。このクラスはファイル ハンドルをカプセル化し、ファイルに対する各種のオペレーションを提供するもの。System.IO.FileStream の実装としては、OS の提供するファイルハンドルを管理する必要があるので、直接これを操作しているものと想像できる。上述の通り、ファイル ハンドルはアンマネージド リソースなのだけれど、アンマネージド リソースを管理する System.IO.FileStream というクラス(のオブジェクト)はマネージ オブジェクトという扱いになる。

なので、アンマネージ オブジェクトを意識することはあまり(絶対ではない)ないと考えられる。

さて、ここで上記の Test の派生クラスを定義する場合のことを考える。そこでも IDisposable の機能を定義したいとしたらどうしようか?この時に基本クラスの実装には手を付けないのが前提。

解答だけを示すならこうなる。

  • 派生クラスでは IDisposable インターフェースの再実装をしない。
  • 派生クラスで Dispose(disposing As Boolean) メソッドをオーバーライドする。
     処理は上記と一緒だけれど、以下の変更を施す必要がある。
    • Overridable を Overrides に変更する。
    • メソッドの最後で基本クラスの Dispose(disposing As Boolean) メソッドを呼び出す。
  • disposedValue フィールドは、基本クラスで定義されているものを流用しちゃいけない。
     もともと Private になっているので問題ないだろうけど、これを書き換えて無理やりアクセスしないこと。

要は、既に実装されている Dispose( ) メソッドから、独自の実装を施した Dispose(disposing As Boolean) を呼び出してもらえるようにするだけ。

これだけなら、単に仮想メソッドによる機能の追加になるのだけれど、よくよく見ると大きな問題が隠れている。

IDisposable インターフェースによって仕様が決められているのは、Dispose( ) メソッドだけであって、独自に定義し、派生クラスでオーバーライドした Dispose(disposing As Boolean) に関する仕様はどこにも明示されていない。

今回は基本クラスも派生クラスも自作しているので、Dispose(disposing As Boolean) の仕様はわかるけれど、前提が変わってしまった場合には上記の実装は結構危ういものになってしまことがわかる。Visual Studio のような開発環境を使用していると、メソッドのシグネチャ等の情報も提供してくれるけれど、実装については正確にわからない可能性もある。

上記は Microsoft が定めるガイドラインに沿った記述であり、Microsoft 謹製のツールが吐くコードなので問題になることは多くはないと思うけど、基本クラスを他者が作成している場合等も考えると絶対安全とも言えないところがある。

今更ながらではあるけど、今回のサンプルコードは Visual Basic のものを採用している。C# を使っても同様のコードを記述することも可能だし、Visual Studio がスニペットの挿入をサポートしているのでその機能を利用することも可能。ただ、その時にいくつかの選択肢が提示される。「Dispose パターンを使って(明示的に)インターフェースを実装します」を選択してやらないとなんの機能も実装していない Dispose( ) メソッドが構築されてしまう。Visual Basic ではなんの選択肢も提示されずにガイドライン通りのコードが生成される。紛れがなくていいのか、なんでもできるのがいいのか、好みの別れるところであるとは思うけど、、、。

 


nice!(0)  コメント(0)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この記事のトラックバックURL: