CodeZine(コードジン)

特集ページ一覧

C#のデバッグテクニック

デバッグ時のメタ情報取得

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/04/01 00:00

ダウンロード ソースコード (9.9 KB)

スケーラブルなデバッグシステムの設計に取り組んでいるときにぶつかった問題を解決してくれたC#のテクニックを解説。

はじめに

 私にとってGUIアプリケーションのデバッグとは、ほとんどの場合、デバッグステートメントをダイアログボックスの形式で表示することだ。このテクニックは小規模から中規模のアプリケーションでは有効だ。しかし大規模なアプリケーションの場合、ステートメントごとにダイアログボックスがポップアップさせるのは非生産的だ。そこで、実行時にデバッグステートメントを表示するもっと良い方法はないかと考えた結果、目に留まったのがC#である。

 C#は、私が便利でスケーラブルなデバッグシステムの設計に取り組んでいるときにぶつかった3つの問題を解決してくれた。私は普段JavaやC/C++を主に使用しているが、これらのプログラミング言語では次のような問題があった。

  1. 行番号、メソッド名などのメタ情報が十分に得られない
  2. デバッグの焦点が移るたびに、デバッグステートメントの追加と削除を行う必要がある
  3. デバッグステートメントをプログラム内にコンパイルするとパフォーマンスに悪影響が出る

3つの問題の解決策

 では、これらの問題に対する解決策を順番に見ていこう。まずは1番目の問題である。この問題は、基本的にSystem.ReflectionおよびSystem.Diagnostics名前空間のいくつかのクラスによって解決できる。System.Diagnostics.StackFrameクラスを使用すると、コールスタックを調査して、現在のレベルよりも上のレベルに何があるか、どの行から関数が呼び出されたか、などの詳細を知ることができる。また、System.Reflectionクラスを使用すると、関数、名前空間、変数型などの名前を確認できる。これらのクラスを利用したのが次のコードである。このコードでは、それを呼び出した関数のファイル名、行番号、メソッド名を取得している。

// create the stack frame for the function that
// called this function
StackFrame sf = new StackFrame( 1, true );

// save the method name
string methodName = sf.GetMethod().ToString();

// save the file name
string fileName = sf.GetFileName();

// save the line number
int lineNumber = sf.GetFileLineNumber();

 次は2番目の問題、実行時にプログラム内のさまざまなセクションを選択してデバッグするにはどうしたらいいかだ。この問題は、1番目の問題に関係している。1番目の問題を解決することで得られる情報が、デバッグステートメントの表示をフィルタリングするのに役立つからだ。次の例では、名前空間に基づいてフィルタリングを行うことにする。例えば、プログラム内に15の名前空間があり、そのうち1つの名前空間のデバッグステートメントだけを表示したいとする。この場合、デバッグクラスに、その名前空間だけがデバッグステートメントを表示するように指示するだけでよい。

 コード例は次の通り。

// create the namespaces hashtable
  namespaces = new Hashtable();

  // get the assembly of this class
  Assembly a = Assembly.GetAssembly( new Debug().GetType() );

  // now cycle through each type and gather up all the namespaces
  foreach( Type type in a.GetTypes() )
  {
    // check if the namespace is already in our table
    if( ! namespaces.Contains( type.Namespace ) )
      namespaces.Add( type.Namespace, true );
  }

 上記のコードでは、このコードを含んでいるDebugクラスと同じアセンブリ内にあるすべての名前空間を格納するHashtableオブジェクトを作成する。しかし、これでは問題の半分が解決しただけである。残りの半分、つまり実際のフィルタリングコードは次のようになる。

// only proceed if the namespace in question is being debugged

 if( (bool) namespaces[ method.DeclaringType.Namespace ] )

   // this is where the debug statement display code would be

 ここまでは上出来だ。残る問題は、プログラムの最終リリースを作成するときに、厄介なデバッグステートメントを、1つ1つ手作業でコメントアウトするか削除するような手間をかけずに削除することだ。ここで役に立つのが"属性"だ。ここでは皆さんが既にC#における属性の動作を知っているものと想定して概念的な説明を省き、前述のSystem.Diagnostics名前空間に含まれているConditionalAttributeの説明に進みたいと思う。

 次に、ConditionalAttributeクラスの使用例を示す。

  [Conditional("DEBUG")]
  public void Debug( string msg )
  {
    // this is where the debug statement
    // display code would be
  }

 このように記述すると、プログラムのコンパイル時に、#defined'DEBUG'が指定されているかどうかがチェックされ、定義されている場合はこの関数がそのまま残される。しかし、#defined'DEBUG'が指定されていない場合は、この関数に対するすべての呼び出しがコンパイルから除外され、パフォーマンスに影響を与えることがなくなる。

まとめ

 ここで取り上げた問題のうち、いくつかはC/C++やJavaでも解決できると主張する人がいるかもしれないが、どちらの言語もすべての問題を解決することはできない。たとえばC/C++では、#define#ifdefを使用して、C#のConditionalAttributeクラスと同様のことを実現できる。しかし、C/C++で有益なメタ情報を入手するには限界がある。また、Javaではメタ情報を入手することは可能だが、私の知る限り、すべてのコードが必ずコンパイルされてしまう(つまり、条件コンパイルの機能はない)。C#ならば、上記の問題をすべて(しかも洗練された方法で)解決し、なおかつそれ以上のことが可能である。

 以上のテクニックの具体例を示すために、小さなWindowsアプリケーションを作成してみた。このDebugクラスは他のプロジェクトにも挿入可能である。ぜひお試しいただきたい。



  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • Mike Borromeo(Mike Borromeo)

    ミシガン大学工学部生。普段C/C++、Java、C#、PHP、VBScript、JavaScript、MSSQL、HTMLを利用している。連絡先はMikeD227@hotmail.com。本稿のアイデアおよびコードは、GnutellaクライアントプロジェクトPhoslの一環として開発されたものである...

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5