#ConditionalAttribute
ログ用関数などによく使うConditionalAttribute
という属性クラスがあります。
これは、voidを返すメソッドにつけると、Defineされたシンボル(DEBUG
とか)の状態によってそのメソッドの呼び出しを有効にしたり無効にしてくれる属性です。
static void Main(string[] args)
{
Log();
Console.ReadKey();
}
[Conditional("LOG")]
static void Log()
{
Console.WriteLine("YukaMaki");
}
#実行時評価される引数
例えば上記のようにすると、LOG
シンボルが定義されているときのみコンソールに文字列が表示されます。では次のようにするとどうなるでしょうか?
static void Main(string[] args)
{
Log("Hello World!" + TooHeavy());
Console.ReadKey();
}
[Conditional("LOG")]
static void Log(string msg)
{
Console.WriteLine(msg);
}
// 処理に1秒ぐらいかかるとする
static string TooHeavy() => DateTime.Now.ToString();
よく見る光景です。もし、引数はちゃんと評価されているのであれば、本番ビルドでもLog
を通るたびに無駄な処理と無駄な文字列のアロケーションがされていることになります。
果たして、引数は評価されているのでしょうか?
static void Main(string[] args)
{
Log("Hello World!" + TooHeavy());
Console.WriteLine(isCalledHeavy);
Console.ReadKey();
}
[Conditional("LOG")]
static void Log(string msg)
{
Console.WriteLine(msg);
}
static bool isCalledHeavy = false;
static string TooHeavy()
{
isCalledHeavy = true;
return DateTime.Now.ToString();
}
引数の生成に副作用を持たせてみました。評価されるとisCalledHeavy
が立つので評価されたことがわかります。
False
結論、引数も評価されなくなりました。無駄な処理も生まれずに安心安心…というわけにはいかず、これは逆に危ないかもしれません。
// OK
HogeClass hoge = null;
bool isSuccess = TryParse(ref hoge);
Log(isSuccess);
// hogeはnullのままだ!!
HogeClass hoge = null;
Log(TryParse(ref hoge));
下のように書いてしまうと、LOG
シンボルの有無で全然処理が変わってしまいます。この場合、この後、Null例外が出て「リリースビルドだけばぐってる~~!!😵😵😵」になってしまうでしょう…
この場合は必要な処理が欠ける方ですが、3のソースで、Logの行が長いからと言って、引数をローカル変数に分割した場合には無駄な処理が発生する原因になりえます。
おまけ
string msg = "Hello World!" + TooHeavy();
Log(msg + "_"); // 引数を処理させるためにちょいと加工
これをコンパイルしてみます。
IL_0000: ldstr "Hello World!"
IL_0005: call string Program::TooHeavy()
IL_000a: call string [System.Runtime]System.String::Concat(string, string)
IL_000f: pop
IL_0016: ret
しっかり呼ばれていることが確認できますね。
Conditionalメソッドは、行そのものがなかったことにされています。
TooHeavey
が完全に副作用をもたらさない場合、JITによって消える可能性はある気はします。