ConditionalAttributeをメソッドに対して指定すると、条件付きコンパイル定数が定義されている場合のみメソッドの呼び出しを行うようにすることが出来ます。 この属性は#ifディレクティブと似た機能を持つものですが、ここではその違いについて見ていきます。

#ifディレクティブ

Conditional属性の説明に入る前に、#ifディレクティブについて軽く触れておきます。 #ifディレクティブは条件付きコンパイルを行うための構文で、例えばデバッグビルドの時だけデバッグ用のコードを有効(または無効)にするといったような場合に使用します。 また、#defineディレクティブ(VBでは#Constディレクティブ)を用いてコード上で条件付きコンパイル定数(シンボル)を定義することもできます。

// イベントを記録する際に、時刻も記録する
//#define LogEventTime

using System;
using System.Threading;

class Sample {
  // イベントを記録する
  static void LogEvent(string message)
  {
#if DEBUG
#if LogEventTime
    Console.WriteLine("{0}> {1}", DateTime.Now, message);
#else
    Console.WriteLine(message);
#endif
#endif
  }

  static void Main()
  {
    // イベントを記録
    LogEvent("Start application");

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

    // いくつもの処理があるとする
    Thread.Sleep(1000);

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

    // イベントを記録
    LogEvent("End application");
  }
}
' イベントを記録する際に、時刻も記録する
'#Const LogEventTime = True

Imports System
Imports System.Threading

Class Sample
  ' イベントを記録する
  Shared Sub LogEvent(ByVal message As String)
#If DEBUG Then
#If LogEventTime Then
    Console.WriteLine("{0}: {1}", DateTime.Now, message)
#Else
    Console.WriteLine(message)
#End If
#End If
  End Sub

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

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

    ' いくつもの処理があるとする
    Thread.Sleep(1000)

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

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

まず、このコードで#ifディレクティブを使用しているLogEvent()メソッドについて見てみます。 このメソッドの動作を簡単に説明すると、デバッグビルドの場合ではシンボル'DEBUG'が宣言されているのでメソッド内のコードがアクティブになります。 また、シンボル'LogOutputTime'が有効になっている場合は時刻を出力する方のコードがアクティブになり、 無効になっている場合はメッセージのみを出力する方のコードがアクティブになります。 コード上では#define(#Const)をコメントアウトしているためシンボル'LogOutputTime'は無効になっています。

リリースビルドとデバッグビルド、またシンボル'LogOutputTimeの値を変えた場合の実行結果は次のようになります。

デバッグビルド、シンボル'LogOutputTime'が有効な場合
2003/03/30 11:42:46: Start application
Begin calculation.
Calculation was finished.
2003/03/30 11:42:47: End application
デバッグビルド、シンボル'LogOutputTime'が無効な場合
Start application
Begin calculation.
Calculation was finished.
End application
リリースビルドの場合
Begin calculation.
Calculation was finished.

ここではこれ以上条件付きコンパイルについては踏み込まないので、より詳しくは以下のドキュメントを参照してください。

#ifディレクティブの問題点

なぜ最初に#ifディレクティブを紹介したかというと、#ifディレクティブには不便な点があるからです。 というのは、前の例のようにデバッグ時のみあるメソッドを実行したいときには、メソッドの内のコードだけを#ifディレクティブをくくらなければなりません。 メソッド宣言全体を#ifでくくってしまうと、リリースビルドの時点ではこのメソッドはコンパイルされなくなり、呼び出しが無効になるからです。

using System;

class Sample {
  // デバッグ時のみイベントを記録したい
#if DEBUG
  static void LogEvent(string message)
  {
    Console.WriteLine(message);
  }
#endif

  static void Main()
  {
    // リリースビルドではLogEventメソッドが存在しないためコンパイルエラーとなってしまう
    LogEvent("Start application");
  }
}
Imports System

Class Sample
  ' デバッグ時のみイベントを記録したい
#If DEBUG Then
  Shared Sub LogEvent(ByVal message As String)
    Console.WriteLine(message)
  End Sub
#End If

  Shared Sub Main()
    ' リリースビルドではLogEventメソッドが存在しないためコンパイルエラーとなってしまう
    LogEvent("Start application")
  End Sub
End Class

これは呼び出し側で#ifディレクティブを適用すれば解決できますが、その数が多くなると非常に面倒です。

using System;

class Sample {
  // デバッグ時のみイベントを記録したい
  static void LogEvent(string message)
  {
    Console.WriteLine(message);
  }

  static void Main()
  {
    // これでコンパイルエラーにはならなくなるが
#if DEBUG
    LogEvent("Start application");
#endif

    Console.WriteLine("Hello, world!");

    // 呼び出しの数だけ#ifを記述するのは面倒
#if DEBUG
    LogEvent("End application");
#endif
  }
}
Imports System

Class Sample
  ' デバッグ時のみイベントを記録したい
  Shared Sub LogEvent(ByVal message As String)
    Console.WriteLine(message)
  End Sub

  Shared Sub Main()
    ' これでコンパイルエラーにはならなくなるが
#If DEBUG Then
    LogEvent("Start application")
#End If

    Console.WriteLine("Hello, world!")

    ' 呼び出しの数だけ#Ifを記述するのは面倒
#If DEBUG Then
    LogEvent("End application")
#End If
  End Sub
End Class

また、メソッド内のコードのみに適用した場合でも、リリースビルド時でもメソッドは呼び出されるので、無用なオーバーヘッドが生じます。

using System;

class Sample {
  // デバッグ時のみイベントを記録したい
  static void LogEvent(string message)
  {
#if DEBUG
    // これでコンパイルエラーにもならず、記述もシンプルになるが
    Console.WriteLine(message);
#endif
  }

  static void Main()
  {
    // リリースビルドでもメソッド呼び出しは行われてしまう
    LogEvent("Start application");

    Console.WriteLine("Hello, world!");

    LogEvent("End application");
  }
}
Imports System

Class Sample
  ' デバッグ時のみイベントを記録したい
  Shared Sub LogEvent(ByVal message As String)
#If DEBUG
    ' これでコンパイルエラーにもならず、記述もシンプルになるが
    Console.WriteLine(message)
#End If
  End Sub

  Shared Sub Main()
    ' リリースビルドでもメソッド呼び出しは行われてしまう
    LogEvent("Start application")

    Console.WriteLine("Hello, world!")

    LogEvent("End application")
  End Sub
End Class

Conditional属性

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

using System;
using System.Diagnostics;

class Sample {
  // デバッグ時のみイベントを記録したい
  [Conditional("DEBUG")]
  static void LogEvent(string message)
  {
    Console.WriteLine(message);
  }

  static void Main()
  {
    LogEvent("Start application");

    Console.WriteLine("Hello, world!");

    LogEvent("End application");
  }
}
Imports System
Imports System.Diagnostics

Class Sample
  ' デバッグ時のみイベントを記録したい
  <Conditional("DEBUG")> _
  Shared Sub LogEvent(ByVal message As String)
    Console.WriteLine(message)
  End Sub

  Shared Sub Main()
    LogEvent("Start application")

    Console.WriteLine("Hello, world!")

    LogEvent("End application")
  End Sub
End Class
デバッグビルドの場合
Start application
Hello, world!
End application
リリースビルドの場合
Hello, world!

この結果を見てわかるとおり、その機能は一見#ifディレクティブと同じですが、リリースビルドの時、つまりシンボル'DEBUG'が定義されていない場合はメソッドの呼び出しが無視されます。 Conditional属性が適用されているメソッドの呼び出しは、コンパイル時に無視されるようになるため、呼び出し自体も起こりません。

Conditional属性は単に条件によって呼び出しを無視するだけなので、Conditional属性を適用したメソッド内でも当然#ifディレクティブは使用可能で、Conditional属性が適用されていない場合と同様に機能します。

Conditional属性に指定する条件付きコンパイル定数

Conditional属性に指定するシンボル名は、大文字小文字が区別されます。 このシンボルは次のいずれかの方法で定義出来ます。 Conditional属性は、これらのシンボルが定義されている場合のみ有効になります。

  • ソースコード中のディレクティブ (#define/#undefや#Constによる定義)
  • コンパイラのコマンドラインオプション (/defineオプションによる定義)

Conditional属性を適用できるメソッド

また、Conditional属性を適用するメソッドは値を返さないメソッド(void型/Subプロシージャ)に限られます。 Conditional属性でメソッド呼び出しが無視された場合、戻り値がどうなるか定義できないためです。

もしどうしても値を返したい場合は、returnの変わりに引数をref/ByRefにするなどの方法をとる必要があります(C#ではoutパラメータを使うことも出来ません)。 戻り値を返すメソッドや、引数にoutパラメータのあるメソッドに対してConditional属性を適用しようとするとコンパイルエラーとなります。

using System;
using System.Diagnostics;

class Sample {
  // デバッグ時のみイベントを記録したい
  [Conditional("DEBUG")]
  static void LogEvent(string message, ref DateTime dateTime)
  {
    Console.WriteLine(message);

    dateTime = DateTime.Now;
    //return DateTime.Now;
  }

  static void Main()
  {
    DateTime dt;

    LogEvent("Start application", ref dt);

    Console.WriteLine("Hello, world! {0}", dt);

    LogEvent("End application", ref dt);
  }
}
Imports System
Imports System.Diagnostics

Class Sample
  ' デバッグ時のみイベントを記録したい
  <Conditional("DEBUG")> _
  Shared Sub LogEvent(ByVal message As String, ByRef dateTime As DateTime)
    Console.WriteLine(message)

    dateTime = DateTime.Now
    ' Return DateTime.Now
  End Sub

  Shared Sub Main()
    Dim dt As DateTime

    LogEvent("Start application", dt)

    Console.WriteLine("Hello, world! {0}", dt)

    LogEvent("End application", dt)
  End Sub
End Class
デバッグビルドの場合
Start application
Hello, world! 2003/03/30 22:15:14
End application
リリースビルドの場合
Hello, world! 0001/01/01 0:00:00

複数のConditional属性

Conditional属性を複数指定することもできます。 複数指定した場合は、定義されているシンボルが1つでもある場合、そのメソッドに対する呼び出しは行われます。 つまり、OR条件でConditional属性が有効になります。 次の例では、シンボル'DEBUG'もしくはシンボル'TEST'が定義されている場合に、ログが出力されるようになります。 コメントアウトしてあるシンボルの定義を有効にすると結果が変わります。

//#define TEST

using System;
using System.Diagnostics;

class Sample {
  [Conditional("DEBUG"), Conditional("TEST")]
  static void LogEvent(string message)
  {
    Console.WriteLine(message);
  }

  static void Main()
  {
    LogEvent("Start application");

    Console.WriteLine("Hello, world!");

    LogEvent("End application");
  }
}
' #Const TEST = True

Imports System
Imports System.Diagnostics

Class Sample
  <Conditional("DEBUG"), Conditional("TEST")> _
  Shared Sub LogEvent(ByVal message As String)
    Console.WriteLine(message)
  End Sub

  Shared Sub Main()
    LogEvent("Start application")

    Console.WriteLine("Hello, world!")

    LogEvent("End application")
  End Sub
End Class