解説

実例で学ぶWindowsプログラミング

第3回 ユーザビリティを高めるファンクション・バーの実装(前編)  

デジタルアドバンテージ 一色 政彦
2005/02/23
Page1 Page2 Page3

■コード上の重要な変化(リソース破棄の仕組み)

 前述のImageListコンポーネントの追加作業により、FunctionBar.csは次のようなコードになる。

[ToolboxBitmap(typeof(ToolBar))]
public class FunctionBar : System.Windows.Forms.ToolBar
{
  private System.Windows.Forms.ImageList imageList1;
  private System.ComponentModel.IContainer components;

  public FunctionBar()
  {
    InitializeComponent();
  }

  protected override void Dispose( bool disposing )
  {
    if( disposing )
    {
      if( components != null )
        components.Dispose();
    }
    base.Dispose( disposing );
  }

  {
    this.components = new System.ComponentModel.Container();
    this.imageList1 = new System.Windows.Forms.ImageList(this.components);
    //
    // imageList1
    //
    this.imageList1.ImageSize = new System.Drawing.Size(16, 16);
    this.imageList1.TransparentColor = System.Drawing.Color.Transparent;
  }
}
ImageListコンポーネントを追加した後のFunctionBarコントロールのソース・コード
コード中の太字部分がここで「重要な変化」と呼んでいる部分。

 上記コードにおけるその「重要な変化」とは、「components」という名前のIContainerインターフェイス型のインスタンス変数に対して、Containerオブジェクト(System.Component名前空間)を生成して割り当て(以降、このオブジェクトは「IContainerオブジェクト」と表記する)、さらにそのオブジェクトがImageListコンポーネントのコンストラクタにパラメータとして指定されているところだ(コードの太字部分を参照)。

 なぜこれが重要かというと、これは「コンポーネントのリソース管理(リソースの破棄)を行うための仕組み」だからである。なおかつ、この仕組みがクラスに含まれている「基本機能」として提供されているわけではなく、VS.NETが自動生成するコードによって「後付けの追加機能」として提供されているということを、強く認識することがより重要だ。つまり、この仕組みをよく理解せずにコンポーネントを作成したり、不要と思ってこのコードを削除してしまったりすれば、せっかく用意されているコンポーネントのリソース管理の仕組みが失われてしまうことにもなり得るからだ。

 とはいっても「.NETでは、ガベージ・コレクタが使い終わったリソースを自動的に破棄してくれるから、わざわざソース・コード上で管理する必要はない」と思った読者もいるかもしれないが、ガベージ・コレクタはリソース・リークなどを防ぐ最終手段だと考えてほしい。.NETであっても、基本的に、リソースは使い終わったらすぐに解放することが望ましいのだ。

 そこで.NETでは、(ガベージ・コレクタによる暗黙的なリソース破棄に頼らずに)ユーザーが自ら明示的にリソースを破棄するためのメソッドとして、Disposeメソッドが用意されている(このメソッドは、System.ComponentModel名前空間のComponentクラスのメソッドで、System名前空間のIDisposableインターフェイスのDisposeメソッドを実装したものである)。

■Disposeメソッドが呼び出される順序

 このDisposeメソッドが呼び出されると(例えば、フォームが閉じられるとそのフォームに含まれるコントロールのDisposeメソッドが呼び出される。ここでは仮にFunctionBarコントロールのDisposeメソッドが呼び出されたことにする)、下に示す流れに従って、次々と各クラスのDisposeメソッドが呼び出されることになる。

(1)FunctionBarクラスのDispose( )メソッド(実際にはComponentクラスのメソッド)
 

→ ユーザーからの明示的な呼び出し。このメソッドの内部でthis.Dispose(True)が呼び出され、(2)の処理へ移行する。

 

→ もしくは、ガベージ・コレクタによる(Finalize)デストラクタからの暗黙的な呼び出し。内部でthis.Dispose(False)が呼び出され、(2)の処理へ移行する。

 
(2)FunctionBarクラスのDispose( bool disposing )メソッド
 

→ 解放すべきリソースがあればここで解放する。

 

→ 上記コードにもあるように、最後にbase.Dispose( disposing )が呼び出され、(3)の処理へと移行する。

 
(3)すべての基底クラスのDispose( bool disposing )メソッド
 

→ 以後、連鎖的にbase.Dispose( disposing )が呼び出され、すべてのクラスのリソースが破棄される。

 Disposeメソッドについては、「簡単!Visual Studio .NET入門」という連載の「Disposeメソッドで終了処理」でも説明しているので併せて参照してほしい。ちなみに前回もコード上にこのDisposeメソッドは登場している。

■コンポーネントのリソースがすぐに破棄される理由

 このDisposeメソッドの処理が、先ほどのIContainerオブジェクト(「components」変数)の生成とImageListコンポーネントのコンストラクタでの指定に大きくかかわっている。上に掲載したコードをもう一度確認してみると、FunctionBarクラスのDispose( bool disposing )メソッドの内部に、次のようなIContainerオブジェクトのDisposeメソッドを呼び出す記述があるはずだ。

components.Dispose( );

 これが意味することは、FunctionBarクラスがDispose(破棄)されれば、内部のImageListコンポーネントも(「components」変数に登録されているため)同時にDispose(破棄)されるということだ(ImageListコンポーネントがいつ「components」変数に登録されるかは後述する)。従って、この仕組みに正しく乗れば、コンポーネントに含まれるリソースを自動的に破棄できるわけである。

■コンポーネントをリソース破棄の仕組みに対応させるには?

 しかし、Windowsアプリケーションの開発経験のある読者ならば分かると思うが、すべてのコンポーネントがこのリソース破棄の仕組みを利用するわけではない(例えば、標準のEventLogコンポーネントは、この仕組みを利用しない)。つまり、フォームやコントロールなどにコンポーネントを追加したからといって、必ずしも前述のIContainerオブジェクトの生成やコンポーネントのコンストラクタへの指定は行われないのである。

 それでは、ImageListコンポーネントのようにIContainerオブジェクトが生成されるコンポーネントと、EventLogコンポーネントのように生成されないコンポーネントの違いは一体何なのだろうか。

 実はこの違いは、そのコンポーネントのクラスが、IContainerオブジェクトをパラメータに受け取るコンストラクタをサポートしているかどうかによる。例えば、ImageListコンポーネントは次のようなコンストラクタが提供されている。

public ImageList( IContainer components );
{
  components.Add(this);
  ……中略……
}

 なおこのコンストラクタでは、上の記述のように、パラメータに引き渡されたIContainerオブジェクトのAddメソッドを使って、コンポーネント自身のインスタンス(この場合は「this」)がIContainerオブジェクトに登録されるようである。ここで登録されるおかげで、前述の「components.Dispose( );」がリソース破棄の仕組みとして働くようになるわけである(つまりここが、前述のImageListコンポーネントが「components」変数に登録されるタイミングである)。

 一方、EventLogコンポーネントには、IContainerオブジェクトをパラメータに取るコンストラクタを持っていない。このため、VS.NETはIContainerオブジェクトをパラメータとして指定できず、結果的にIContainerオブジェクトは生成されないのだ。

 まとめると、例えば独自に作成したコンポーネントで画像などの何らかのリソースを作成した場合に、そのリソースの破棄を自動的に行うためには、そのコンポーネント・クラスにIContainerオブジェクトをパラメータに取るコンストラクタを用意して、そこでAddメソッドを使って自身のインスタンスを登録しなければならないということになる。このようにしておけば、独自のコンポーネントでもVS.NETがそのコンストラクタを呼び出すコードを自動生成してくれる。忘れないように注意してほしい。

■フォームおよびコントロールのリソース破棄について

 なおこのようなコンポーネントのリソース破棄の仕組みは、(もうお気づきだと思うが)コントロールだけでなくWindowsフォームのひな形コードでも提供されている。「簡単!Visual Studio .NET入門」の「components変数」では詳しい説明が割愛されているが、その内容は上記のとおりだ(この場で改めて説明させていただいた)。

 また、フォーム上に配置されているコントロールのリソース破棄(Disposeメソッド呼び出し)は、フォームのControlsプロパティにより管理されているようだ。つまり、コントロールはControlsプロパティ(=基本機能)で、コンポーネントはIContainerインスタンス変数(=後付けの追加機能)で、それぞれリソース破棄の管理をしているわけである。

 これらのリソース破棄の仕組みは、フォームが閉じられるタイミングで、(それぞれのDisposeメソッドが呼び出されて)実行される。ただし、そのフォームがモーダル・ダイアログ(つまりShowDialogメソッドにより表示したフォーム)の場合、Disposeメソッドが自動的に呼び出されないので、注意が必要だ(もしすぐにフォームが破棄されたとすれば、それはガベージ・コレクタによる破棄を意味する)。よって、ShowDialogメソッドを使った後は、必ずそのDisposeメソッドを呼び出すようにしたい。

 以上、説明が少し長くなってしまったが、再び、ファンクション・バーの実装方法に話を戻そう。


 INDEX
  解説:実例で学ぶWindowsプログラミング
  第3回 ユーザビリティを高めるファンクション・バーの実装(前編)
    1.ファンクション・バーとは何か?
  2.VS.NETが提供するリソース破棄の仕組み
    3.ファンクション・バーの実装方法
 
インデックス・ページヘ  「解説:実例で学ぶWindowsプログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH