CLRから見たリソースについて

少し変わった話になりますが、hilaponさんにご連絡をいただいて「マネージリソースのみで構成されているクラスにIDiposaleを実装するメリット」という議論がMSDNフォーラムで盛り上がっているというのを知りました。この議論に参加する予定はありませんが、議論の的になっているリソースという言葉を私が理解しているCLRの側面から、少しだけ解説しようと思います。

最初にリソースという言葉の定義です。リソースとは、英語のResourcesをカタカナ読みしただけですが、英語の意味としては「資源」とか「資産」になります。平たく表現するとすれば、プログラムが実行時に必要とする様々な資源(CPU、メモリ、HDD、etc)を指す言葉になります。プログラムにとってのリソースを大雑把に分類すると、以下の2種類になります(異論はあるでしょうが、こんな分類もあるというだけです)。

  • プログラムを格納するファイル(つまりPortable Executable-PE-ファイル)内に持っているリソース
  • PEファイルの外部にあるリソース

PE内のリソースは、CLRから考えると2種類に分類できます。それは、「アンマネージ リソース」と「マネージ リソース」です。アンマネージ リソースとは、アイコンとかエクスプローラーでファイルのプロパティを参照した時に確認することができる情報(バージョンとか)のことです。フリーソフトなど公開されているリソースエディタなどが扱うのが、このアンマネージリソースです。そしてマネージ リソースとは、メタデータとして規定されているマニフェスト リソースから参照されるPE埋め込みファイルなどのことです(メタデータではManifestResource表がマニフェスト リソースです)。

次にプログラムが動作する時の資源として、マネージプログラムが使用するインスタンスやOSが提供するファイルオブジェクト(実際にはファイル ハンドルを利用します)などのリソースがあります。これらのメモリ上のリソースを、GCで管理できるかどうかを表現したのが以下の図になります。

MemoryByGC

この図には、GCの対象にならないマネージリソースが含まれています。これは、ラージオブジェクトヒープ(LOH)に格納されたオブジェクトを意味しています。LOHに格納されるオブジェクトとは、最初から世代2に格納されるもので、現時点のCLRではGCによる回収が行われません。具体的な例を上げれば、アセンブリなどの型情報になります。なぜなら、型情報はオブジェクトを作成するために必要な情報であり、CLRからはその型のインスタンスを作成する必要があるかないかを判断することができないからです。よってLOHに格納されているオブジェクトを解放するには、AppDomainのアンロードが必要になります。

次にプログラムが内部的に使用するオブジェクトですが、参照型であれ値型であってもヒープメモリから考えると以下の図のような構造になります。

ObjectByHeap

この図に表記しているマネージリソースとは、GCの対象となるマネージヒープに確保された資源を意味します。そしてアンマネージリソースとは、GCで管理できないヒープメモリに確保されたものになります。ここまでで、私が考えているCLRから考えたリソースというものを理解していただけたのでは、ないでしょうか。それは、シンプルに「GCの対象になるかどうか」ということです。

今度はMSDNフォーラムで議論がなされているIDiposableに関する話に入ります。前述までで「GCが管理する=マネージリソース」という私の考えを理解したいただけるのなら、先に示した図の中にある「アンマネージ・ポインタ」をどのように考えるのかという疑問を持たれることでしょう。私の見解は明確で、このアンマネージ・ポインタもマネージリソースの1つだということです。何故なら、ポインタ自体は符号なし整数であり、アンマネージリソースへのアドレスを保持しているに過ぎません。このポインタを介してアンマネージリソースを参照できるとしてもです(参照できるというのは、CLRが提供している相互運用性の機能のお蔭なだけです)。

このように考えた時に問題となるのが、マネージヒープにおけるメモリの回収です。GCと言うのは、基本的にルートから辿っていけるかどうかしか見ませんので、図にあるアンマネージ・ポインタであっても回収を行ってしまいます。GCで回収を行われると問題になるのが、アンマネージリソースです。この部分の解放に誰が責任を持つのかという問題です。こうした場合に使われるデザインパターンがあります。たとえば、ファイルに対するOpen/Closeパターンです。Open/Closeパターンで全てのクラスが実装されているかと言えば、必ずしもそんなことはありません。そうするとGCは、何を頼りに正しいクリーンアップ処理を行ったら良いのかという課題がでてきます。この課題に答えるためにIDiposableが提供されています。

つまりIDiposableとは、GCがメモリを回収する時に正しいクリーンアップを行う機会をオブジェクトに与えるためこそ提供されるということです。その回収対象が、アンマネージリソースかマネージリソースであってもです。よって、IDiposableとは、GCによってリソースをクリーンアップする機会を付与するデザインパターンであり、自分でクリーンアップを行う必要があるオブジェクトが実装すべき設計上のガイドラインだということです。たとえば、CCWやRCWは、内部でCOMの参照カウンタを管理するためにCLRが組み込みで提供するオブジェクトです。このように管理するオブジェクト毎に、特別の管理が必要であれば、そのクリーンアップのためにIDiposableを実装すべきです。マネージオブジェクトだけを使っていたとしても、大量のメモリを消費するようなアプリの場合は、IDiposableを実装して明示的にGCが回収できるようにすべきですし、そのような要件があるのであれば必要に応じてGCを起動する必要もあることでしょう。つまり、MSDNフォーラムの議論の最初の話題としての「マネージオブジェクトにIDiposableを実装する必要があるか」ということに関する私の考えは、メモリの使用率などに対する要求があれば「実装すべき」で、そのような機能要求がない限りは「必要ない」ということになります。従って、どのような要件があるかで判断すべきことだということです。