KEN's .NET

[特集5] Debug.Writeの仕掛け 〜 Conditional属性 〜

ホーム > KEN's .NET > [特集5] Debug.Writeの仕掛け 〜 Conditional属性 〜

System.Diagnostics.DebugクラスのWriteメソッドのように開発環境のDebugビルドでは有効になりReleaseビルドにすると無効になる、 この仕掛けについて説明します。

1.はじめに


私は開発環境を持っておらずエディタ+コンパイラで遊んでいるので、 Debugクラスは使わず、いつもMessageBox.ShowやConsole.WriteLineクラスなどを使っていますが、 開発環境を使われている方々であれば、最も多用しているクラスの一つでしょう。 例えば、DebugクラスのWrite/WriteLineメソッドは、仕込んでおいた場所の式や変数の値を表示できて便利です。 しかも開発環境でデバッグビルドしているときは有効に働き、リリースビルドをすると無効になります。 これはどうやって実現しているのでしょうか?

2.Conditional属性


この仕掛けを担っているのは、ずばりConditional属性です。 Conditional属性の話の前に、予備知識として属性とは何か?を簡単に紹介します。 .NETでメソッド、プロパティ、型などに本来の機能以外の性質を付加するために属性を使います。 とりあえず実例を見てもらいましょう。

<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
図1 属性の使用例(DebuggerStepThrough属性)


図1は開発環境でのコーディング時に自動生成されるInitializeComponentメソッドの宣言部分です。 InitializeComponentメソッドには、 開発環境のデザイナ上でフォーム上にぺたぺたと貼り付けたコントロールのインスタンス生成やプロパティを設定するコードが書かれています。 自動で生成されるコードですから、基本的に、普段この中のコードを見る必要はありません。 従来のVBにもありましたが、開発環境の機能としてステップ実行をする機能(コードを順に1ステップずつ実行していく機能)があります。 ステップ実行のとき、このメソッド内部までステップ実行する必要はほとんどないと考えられるので、 メソッド宣言の前に「<System.Diagnostics.DebuggerStepThrough()>」を付加することで、 このメソッドをステップ実行の対象としないようにしてあります。このときの<と>で囲まれた部分を属性といいます。 この中で指定されているものもまたクラスの一つで、System.Diagnostics.DebuggerStepThroughAttributeクラスです。 使用するときは末尾のAttributeの部分は省略します。

さて、本題のConditional属性ですが、これは#If〜#End Ifのようなもので条件付コンパイルに使用できる属性です。 以下にConditional属性の使用例を挙げます。

Imports System
Imports System.Diagnostics

Public Class ConditionalTest
    Public Shared Sub Main()
        DoCondtionalTest()
    End Sub
    <Conditional("ConditionalTest")> _
    Public Shared Sub DoCondtionalTest()
        Console.WriteLine("Conditional属性のテスト。表示されるかな?")
    End Sub
End Class
図2 Conditional属性の使用例(ConditionalTest.vb)


図2のようにConditional属性を利用した場合、 CondtionalAttributeの引き数に指定している"CondtionalTest"が条件付コンパイル定数として扱われます。 #Constディレクティブで条件付コンパイル定数を指定するか、コンパイル時のオプションとして条件付コンパイル定数を指定することができます。 今回はコンパイル時のオプションとして指定するので、図2のコードを図3の(1)、(2)の方法でコンパイルします。 「/d」オプション(/defineの省略形)にて条件付コンパイル定数を指定しています。 この方法の(1)でコンパイルしたときは、DoConditionalTestメソッドが有効に働き、DOS窓にメッセージが表示されます。 (2)でコンパイルしたときは、DoConditionalTestメソッドが無効化され、DOS窓に何も表示されません。

(1) vbc /t:exe /r:System.dll ConditionalTest.vb /d:ConditionalTest=True
(2) vbc /t:exe /r:System.dll ConditionalTest.vb /d:ConditionalTest=False
図3 条件付コンパイルの方法((1)でDoConditionalTestが有効/(2)でDoConditionalTestが無効)

さて、内部的にはどのように処理されているのでしょうか? 「[特集3] 型変換の速度実験と考察」でも利用した方法ですが、ILDASM.exeを使って逆アセンブルし、 (1)、(2)のコンパイルで生成されたバイナリの内部処理の違いを見てみましょう。 下記に(1)、(2)それぞれのMainメソッドのILコードを示します。

(1) vbc /t:exe /r:System.dll ConditionalTest.vb /d:ConditionalTest=True

.method public static void  Main() cil managed
  {
    .entrypoint
    .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) 
    // Code size       6 (0x6)
    .maxstack  8
    IL_0000:  call       void ConditionalTest::DoCondtionalTest()
    IL_0005:  ret
  } // end of method ConditionalTest::Main


(2) vbc /t:exe /r:System.dll ConditionalTest.vb /d:ConditionalTest=False

.method public static void  Main() cil managed
  {
    .entrypoint
    .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) 
    // Code size       1 (0x1)
    .maxstack  8
    IL_0000:  ret
  } // end of method ConditionalTest::Main

図4 コンパイル条件によるILコードの違い(赤色部分が異なる)


図4をご覧いただくとわかるとおり、(1)の場合は、DoConditionalTestメソッドが呼び出されますが、 (2)の場合は、DoConditionalTestメソッドは呼び出されません。ちなみに、(1)、(2)のどちらの場合でも、 DoConditionalTestメソッド自体はILコードに含まれていました。 #If〜#End Ifでもそれで囲んだコードの有効/無効を切り替えられますが、 Conditional属性の方がそのメソッドを呼び出している側のコードすべてを無効化できる点で優れています。 Debugクラスはこの属性を利用して同様の機能を実現しています。Debugクラスのメソッドは条件付コンパイル定数DEBUGに反応します。 Debug.Writeなどを使ったプログラムを作成して、/d:DEBUG=True、/d:DEBUG=Falseのオプションでコンパイルし、 その実行プログラムをILDASMで逆アセンブルしてILコードを比較することで確認できます。 実際に生成される実行ファイル、及び、そのILコードからの判断でなく、 本当にDebugクラスのメソッドにConditional属性が付加されているかどうかはリフレクションを使って確認できます。(図5)

        Dim mi As System.Reflection.MethodInfo
        For Each mi In GetType(System.Diagnostics.Debug).GetMethods()
            Console.WriteLine("メソッド: " + mi.Name)
            Dim obj As System.Object
            For Each obj In mi.GetCustomAttributes(True)
                Console.WriteLine(" 属性: " + obj.ToString())
            Next
        Next
図5 Debugクラスのメソッド、メソッドに付加されている属性を列挙するソースコード


3.まとめ


  • Conditional属性を使えば、Debugクラスのメソッドのようにデバッグ/リリースモードの違いで実行ファイルに呼び出しコードが含まれるかどうかを切り替えることができる
  • Conditional属性による実行制御は、条件付コンパイル定数によりコンパイル時に制御される
  • 条件付コンパイル定数の指定は、コンパイルオプションでは/define:DEBUG=Trueのように行える
以上のようなことがわかりました。

4.ソースコードとILコード

ConditionalTest.vb
CondtionalTestTrue.il(/d:ConditionalTest=Trueの場合)
CondtionalTestFalse.il(/d:ConditionalTest=Falseの場合)

ホーム > KEN's .NET > [特集5] Debug.Writeの仕掛け 〜 Conditional属性 〜

[e-mail] yone_ken00@hotmail.com