System.Diagnostics名前空間にはデバッグ時に便利な属性がいくつか存在します。 ここではそのうちのいくつかを紹介します。 また、それに先だって#Ifディレクティブとその用法を見ていきます。

Sponsored Link

#Ifディレクティブ, #Constディレクティブ

まずはじめに、属性ではありませんが#Ifディレクティブについて見てみます。 #Ifディレクティブは条件付きコンパイルを行うための構文で、例えばデバッグビルドの時だけ特定のコードを有効(または無効)にするといったような場合に使用します。 また、#Constディレクティブを用いて独自に条件付きコンパイル定数を定義することもできます。

Option Strict On

' 時刻も出力する
#Const LogOutputTime = False

Imports System.Threading

Module AttributesForDebugging

    ' イベントを記録する
    Sub LogEvent(ByVal ev As String)

#If Debug Then
#If LogOutputTime Then
        Console.WriteLine(ev + " Time: " + DateTime.Now.ToString())
#Else
        Console.WriteLine(ev)
#End If
#End If

    End Sub

    Sub Main()

        ' イベントを記録
        LogEvent("Start application")

        ' 開始メッセージを出力
        Console.WriteLine("Begin calculation.")

        ' いくつものややこしい処理があるとする
        Thread.Sleep(3000)

        ' 終了メッセージを出力
        Console.WriteLine("Calculation was finished.")

        ' イベントを記録
        LogEvent("End application")

    End Sub

End Module

まず、コードの説明をしますが、Main()のコードはそれほど重要ではありません。 実際に#Ifディレクティブを使用しているLogEvent()メソッドについて見てみます。 このメソッドの動作を簡単に説明すると、デバッグビルドの場合ではDebugスイッチが宣言されているので13行目から19 行目のコードはアクティブになります。 また、LogOutputTimeスイッチにTrueが指定されている場合は15行目がアクティブになり、 Falseの場合は17行目がアクティブになります。 リリースビルドとデバッグビルド、またそのそれぞれでLogOutputTimeの値を変えた場合の実行結果を次に示します。

デバッグビルド LogOutputTime = True
Start application Time: 2003/03/30 11:42:44
Begin calculation.
Calculation was finished.
End application Time: 2003/03/30 11:42:47
Press any key to continue
デバッグビルド LogOutputTime = False
Start application
Begin calculation.
Calculation was finished.
End application
Press any key to continue
リリースビルド
Begin calculation.
Calculation was finished.
Press any key to continue

Conditional属性

なぜ最初に#Ifディレクティブを紹介したかというと、#Ifディレクティブには欠点があるからです。 というのは、前の例のようにデバッグ時だけにあるメソッドを実行したいときにはメソッドの内のコードだけを#Ifディレクティブをくくらなければなりません。 メソッド宣言全体を#Ifでくくってしまうと、リリースビルドの時点ではこのメソッドはコンパイルされなくなり、呼び出しが無効になるからです。 これは呼び出し側にも#Ifディレクティブを適用すれば解決できますが、その数が多くなると非常に面倒です。 また、メソッド内のコードのみに適用した場合でも、リリースビルド時でもメソッドは呼び出されるので、無用なオーバーヘッドが生じます。

この問題を解決するにはConditional属性を使用することができます。 Conditional属性はメソッドに対して使用され、属性のコンストラクタに指定された条件付きコンパイル定数が定義されている時に限りそのメソッドが実際に呼び出されます。 それ以外の場合には実行されることはありません。 次のコードでその使用例を挙げます。

Option Strict On

' 時刻も出力する
#Const LogOutputTime = False

Imports System.Diagnostics
Imports System.Threading

Module AttributesForDebugging

    ' イベントを記録する
    <Conditional("Debug")> _
    Sub LogEvent(ByVal ev As String)

#If LogOutputTime Then
        Console.WriteLine(ev + " Time: " + DateTime.Now.ToString())
#Else
        Console.WriteLine(ev)
#End If

    End Sub

    Sub Main()

        ' イベントを記録
        LogEvent("Start application")

        ' 開始メッセージを出力
        Console.WriteLine("Begin calculation.")

        ' いくつものややこしい処理があるとする
        Thread.Sleep(3000)

        ' 終了メッセージを出力
        Console.WriteLine("Calculation was finished.")

        ' イベントを記録
        LogEvent("End application")

    End Sub

End Module
 _
    Sub LogEvent(ByVal ev As String)

#If LogOutputTime Then
        Console.WriteLine(ev + " Time: " + DateTime.Now.ToString())
#Else
        Console.WriteLine(ev)
#End If

    End Sub

    Sub Main()

        ' イベントを記録
        LogEvent("Start application")

        ' 開始メッセージを出力
        Console.WriteLine("Begin calculation.")

        ' いくつものややこしい処理があるとする
        Thread.Sleep(3000)

        ' 終了メッセージを出力
        Console.WriteLine("Calculation was finished.")

        ' イベントを記録
        LogEvent("End application")

    End Sub

End Module]]>
デバッグビルド
Start application
Begin calculation.
Calculation was finished.
End application
Press any key to continue
リリースビルド
Begin calculation.
Calculation was finished.
Press any key to continue

この結果を見てわかるとおり、その機能は一見#Ifディレクティブと同じですが、リリースビルドの時、つまりDebugが定義されていない場合はメソッドの呼び出しが無視されます。 また、Conditional属性を適用したメソッドでも当然#Ifディレクティブは機能します。 ここで、属性のコンストラクタに渡す文字列は定数名で、大文字小文字が区別されます。 この定数は自分で定義した定数(#Constで定義した定数)でもかまいませんが、 Conditional属性は定数が定義されていて、なおかつ0以外の値を持つときのみ有効になります。

さらに、Conditional属性を適用するメソッドは値を返さないSubメソッドに限られます。 もし何らかの値を返したい場合は、ByRefを使うなどする必要があります。

戻り値が必要な場合の例
Option Strict On

Imports System.Diagnostics
Imports System.Threading

Module AttributesForDebugging

    ' イベントを記録する
    <Conditional("Debug")> _
    Sub LogEvent(ByVal ev As String, ByRef dtm As DateTime)

        dtm = DateTime.Now

        Console.WriteLine(ev + " Time: " + dtm.ToString())

    End Sub

    Sub Main()

        ' この時刻は何らかの理由で利用されるものとする
        Dim dtm As DateTime

        ' イベントを記録
        LogEvent("Start application", dtm)

        ' いくつものややこしい処理があるとする
        Thread.Sleep(3000)

        ' イベントを記録
        LogEvent("End application", dtm)

    End Sub

End Module
 _
    Sub LogEvent(ByVal ev As String, ByRef dtm As DateTime)

        dtm = DateTime.Now

        Console.WriteLine(ev + " Time: " + dtm.ToString())

    End Sub

    Sub Main()

        ' この時刻は何らかの理由で利用されるものとする
        Dim dtm As DateTime

        ' イベントを記録
        LogEvent("Start application", dtm)

        ' いくつものややこしい処理があるとする
        Thread.Sleep(3000)

        ' イベントを記録
        LogEvent("End application", dtm)

    End Sub

End Module]]>
デバッグビルド
Start application Time: 2003/03/22:15:14
End application Time: 2003/03/22:15:17
Press any key to continue
リリースビルド
Press any key to continue

さらに、Conditional属性を複数指定することもできます。 ただ、その場合は定義されていない定数があるか、その定数の値が0であるようなものが一つでもあると、そのメソッドに対するConditional属性は全て無効になります。 その例を挙げますが、実行結果は省略します。 この例では、たとえリリースビルドであってもOutputLogがTrueであれば必ずログが出力されます。

Option Strict On

#Const OutputLog = True

Imports System.Diagnostics
Imports System.Threading

Module AttributesForDebugging

    ' イベントを記録する
    <Conditional("Debug"), Conditional("OutputLog")> _
    Sub LogEvent(ByVal ev As String, ByRef dtm As DateTime)

        dtm = DateTime.Now

        Console.WriteLine(ev + " Time: " + dtm.ToString())

    End Sub

    Sub Main()

        ' この時刻は何らかの理由で利用されるものとする
        Dim dtm As DateTime

        ' イベントを記録
        LogEvent("Start application", dtm)

        ' いくつものややこしい処理があるとする
        Thread.Sleep(3000)

        ' イベントを記録
        LogEvent("End application", dtm)

    End Sub

End Module
 _
    Sub LogEvent(ByVal ev As String, ByRef dtm As DateTime)

        dtm = DateTime.Now

        Console.WriteLine(ev + " Time: " + dtm.ToString())

    End Sub

    Sub Main()

        ' この時刻は何らかの理由で利用されるものとする
        Dim dtm As DateTime

        ' イベントを記録
        LogEvent("Start application", dtm)

        ' いくつものややこしい処理があるとする
        Thread.Sleep(3000)

        ' イベントを記録
        LogEvent("End application", dtm)

    End Sub

End Module]]>

Obsolete属性

Obsolete属性は古くなったメソッドなどに対して適用し、それが使用された場合に警告を発することができる属性です。

Option Strict On

Imports System.Diagnostics

Module AttributesForDebugging

    <Obsolete("このバージョンは使用しないで下さい。 NewMethodを使用して下さい。", True)> _
    Sub OlderMethod(ByVal msg As String)

        Debug.WriteLine("より古いバージョン: " + msg)

    End Sub

    <Obsolete("将来サポートされなくなる可能性があります。 NewMethodを使用して下さい。")> _
    Sub OldMethod(ByVal msg As String)

        Console.WriteLine("古いバージョン: " + msg)

    End Sub

    Sub NewMethod(ByVal msg As String)

        Console.WriteLine("新しいバージョン: " + msg)

    End Sub

    Sub Main()

        OlderMethod("メソッドを呼び出します")

        OldMethod("メソッドを呼び出します")

        NewMethod("メソッドを呼び出します")

    End Sub

End Module
 _
    Sub OlderMethod(ByVal msg As String)

        Debug.WriteLine("より古いバージョン: " + msg)

    End Sub

     _
    Sub OldMethod(ByVal msg As String)

        Console.WriteLine("古いバージョン: " + msg)

    End Sub

    Sub NewMethod(ByVal msg As String)

        Console.WriteLine("新しいバージョン: " + msg)

    End Sub

    Sub Main()

        OlderMethod("メソッドを呼び出します")

        OldMethod("メソッドを呼び出します")

        NewMethod("メソッドを呼び出します")

    End Sub

End Module]]>

このコードをコーディングしているときには次のような警告が表示されます。

表示された警告

さらに、コンパイル時にはこれと同じ警告が発せられます。 このように、Obsolete属性は互換性のために残してあるような仕様の古くなったコードに対して、使用しないように促す警告を発することができます。 この例ではメソッドに対してObsolete属性を適用していますが、アセンブリ、モジュール、パラメータ、戻り値以外の要素に対して適用することができます。 また、警告ではなくエラーとして扱う場合には次のようにします。

Option Strict On

Imports System.Diagnostics

Module AttributesForDebugging

    <Obsolete("このバージョンは使用しないで下さい。 NewMethodを使用して下さい。", True)> _
    Sub OlderMethod(ByVal msg As String)

        Debug.WriteLine("より古いバージョン: " + msg)

    End Sub

    <Obsolete("将来サポートされなくなる可能性があります。 NewMethodを使用して下さい。")> _
    Sub OldMethod(ByVal msg As String)

        Console.WriteLine("古いバージョン: " + msg)

    End Sub

    Sub NewMethod(ByVal msg As String)

        Console.WriteLine("新しいバージョン: " + msg)

    End Sub

    Sub Main()

        OlderMethod("メソッドを呼び出します")

        OldMethod("メソッドを呼び出します")

        NewMethod("メソッドを呼び出します")

    End Sub

End Module
 _
    Sub OlderMethod(ByVal msg As String)

        Debug.WriteLine("より古いバージョン: " + msg)

    End Sub

     _
    Sub OldMethod(ByVal msg As String)

        Console.WriteLine("古いバージョン: " + msg)

    End Sub

    Sub NewMethod(ByVal msg As String)

        Console.WriteLine("新しいバージョン: " + msg)

    End Sub

    Sub Main()

        OlderMethod("メソッドを呼び出します")

        OldMethod("メソッドを呼び出します")

        NewMethod("メソッドを呼び出します")

    End Sub

End Module]]>

つまり、属性のコンストラクタの二つ目の引数にTrueを指定します。 こうすると完全にエラーとなり、この要素を使用したコードをコンパイルしようとするとコンパイルエラーが発生します。

表示されたエラー・警告

さらに、構造体に対してObsolete属性を適用した例を次に挙げます。

Option Strict On

Imports System.Diagnostics

Module AttributesForDebugging

    <Obsolete("System.Drawing.Colorを使用して下さい。")> _
    Structure ARGB

        Dim R As Byte
        Dim G As Byte
        Dim B As Byte
        Dim A As Byte

    End Structure

    Sub Main()

        Dim col1 As ARGB

        Dim col2 As System.Drawing.Color

    End Sub

End Module
 _
    Structure ARGB

        Dim R As Byte
        Dim G As Byte
        Dim B As Byte
        Dim A As Byte

    End Structure

    Sub Main()

        Dim col1 As ARGB

        Dim col2 As System.Drawing.Color

    End Sub

End Module]]>
表示される警告

DebuggerStepThrough属性

DebuggerStepThrough属性は構造体、クラス、メソッド、コンストラクタに対して適用し、適用された要素はデバッグ対象からはずれます。

Option Strict On

Imports System.Diagnostics

Module AttributesForDebugging

    <DebuggerStepThrough()> _
    Sub DebuggedMethod()

        ' デバッグ済みのコードがあるとする

        Console.WriteLine("処理が完了しました。")

    End Sub


    Sub Main()

        DebuggedMethod()

    End Sub

End Module
 _
    Sub DebuggedMethod()

        ' デバッグ済みのコードがあるとする

        Console.WriteLine("処理が完了しました。")

    End Sub


    Sub Main()

        DebuggedMethod()

    End Sub

End Module]]>

例えばこのコードの19行目にブレークポイントを設定し、メソッドにステップ・インしようとしても、ステップ・インせずにメソッドの処理が終了し、メソッド呼び出しのあった次の行に進んでしまいます。 このように、バグの原因を探すために一行ずつ実行するときにデバッグ済みのコードも一行ずつ実行しなければならないのは非常に不便なので、デバッグが完了したメソッドやクラスにDebuggerStepThrough属性を適用することでデバッグ対象から外すことができ、効率的なデバッグが行えるようになります。

ただし、一行ずつ実行することはできなくても、ブレークポイントを設定することができ、実行時には設定されたところで停止します。 また言うまでもありませんが、リリースビルドではDebuggerStepThrough属性を指定していようといなかろうと無関係です。

DebuggerHidden属性

DebuggerHidden属性は、基本的にはDebuggerStepThrough属性と同様のもので、メソッド及びプロパティに対して使用します。 ただDebuggerStepThrough属性とは異なり使用されたメソッドの中ではブレークポイントを設定することができません。 厳密に言うと、ブレークポイント自体の設定はできるもののそのメソッドの外部で停止します。

Option Strict On

Imports System.Diagnostics

Module AttributesForDebugging

    <DebuggerHidden()> _
    Sub DebuggedMethod()

        ' デバッグ済みのコードがあるとする

        Console.WriteLine("処理が完了しました。")

    End Sub

    Sub Main()

        DebuggedMethod()

    End Sub

End Module
 _
    Sub DebuggedMethod()

        ' デバッグ済みのコードがあるとする

        Console.WriteLine("処理が完了しました。")

    End Sub

    Sub Main()

        DebuggedMethod()

    End Sub

End Module]]>

DebuggerHiddenAttributeという名前が表すとおり、デバッグ時にはその要素が「見えなくなる」と解釈するといいと思います。