C# の Assert とライブラリ参照

概要

この記事には C# で Assertion を含むコードを、 別のプログラムがライブラリ参照した場合の挙動について書いています。

背景

Assertion は「プログラミング エラー」をチェックするエラーチェックの一種です。 プログラミングエラーとは、 ある処理を実行する上で必要なの前提条件 (たとえば引数が null でない事) が崩れる事で起こるエラーです。 したがって、崩れてはいけない、 あるいは崩れる事が本来ありえない条件をチェックするために Assertion は使われます。 次に C# 言語の System. Diagnostics. Debug. Assert メソッドを使った簡単な例を示します。

using System;
using System.Drawing;
using System.Diagnostics;

class AssertTest
{
    static void Main( string[] args )
    {
        Rectangle rect = new Rectangle();
        
        // 幅100、高さ100の四角形を作る
        rect.Width = 100;
        rect.Height = 100;
        
        // 面積を計算して表示
        Console.WriteLine( CalcArea(rect) );
    }
    
    // 四角形の面積を求める
    static int CalcArea( Rectangle rect )
    {
        // 幅や高さは負でない数である事
        Debug.Assert( 0 < rect.Width, "幅が負の四角形の面積を求めようとした" );
        Debug.Assert( 0 < rect.Height, "高さが負の四角形の面積を求めようとした" );
        
        // 面積を計算して返す
        return (rect.Width * rect.Height);
    }
}

プログラミングエラーは通常のエラー処理とは異なり、 プログラムの論理が完璧に記述されていればチェックする必要がありません (論理が完璧ならばチェック不要であるエラーをプログラミングエラーと捉えても良いでしょう)。 そのため完成したプログラムにおけるプログラミングエラーのチェックは処理上の無駄となります。 そして基本的には無駄と分かっているコードは削除されるべきですので、 プログラミングエラーのチェックも、 完成したプログラムからは削除されるべきと言えるでしょう。 しかし人間であるプログラマは開発中に必ず前提条件を見落とすミスを犯しますから、 「完成時には不要だから」 という理由ではプログラミングエラーのチェックを省略できません。 つまり、Assertion は製品のコードには含めたくないが開発中には必要、 という話になってきます。

Conditional 属性

C# の Assert メソッドは、 以上の要求を満たすため "DEBUG" というコンパイルシンボル (C でいうプリプロセッサマクロ定数) が定義されていないと呼び出しが行われないようになっています。 そのため Assert メソッドでプログラミングエラーをチェックしておけば、 リリースビルド時に DEBUG シンボルを定義しないだけで一括してプログラミングエラーチェックを削除できます。

コンパイルシンボルの定義に応じて呼び出しを ON/OFF できるメソッドを実装するには、 Conditional 属性を付けて定義します。 次にコンパイルシンボル USE_HOGE が定義されている場合にのみ呼び出されるメソッド Hoge の実装例を示します。

[Conditional("USE_HOGE")] // シンボル USE_HOGE が定義されていない場合は呼び出しが無効
public static void Hoge( int n )
{
    System.Console.WriteLine( n );
}

Conditional 属性の付いたメソッド(以下 「Conditional なメソッド」)を呼び出す文は、 指定シンボルが未定義のままコンパイルされると取り除かれます(恐らく)。 つまり物理的にソースコードから削除されると考えても良いと思います。 文全体が取り除かれるという事は、引数に与えた式の評価が行われないという事です。 次の例を見てみましょう。

using System;
using Diagnostics;

class Program
{
	static int number;
	
	static void Main( string[] args )
	{
	    Hoge( IncrementNum() );
	    Console.WriteLine( number );
	}
	
	static int IncrementNum()
	{
		number = number + 1;
		return number;
	}
	
	[Conditional("USE_HOGE")]
	public static void Hoge( int n )
	{
	    Console.WriteLine( n );
	}
}

メイン関数から Conditional なメソッド Hoge の引数 もしメソッド引数部分に評価を目的としない処理を記述すると、 シンボルが未定義の場合はその処理が実行されないため問題が起こります。

a

さて、ここで実行ファイル一つと DLL 一つで構成されるシステムがあるとします。 もしその DLL 中に Assert を呼び出すコードが含まれる場合、 その動作は少し注意して考える必要があります。 なぜなら、

後書き

厳密に言うと、実行時に呼び出しが行われないというだけで Assert の呼び出しコードは DEBUG シンボルが定義されていない場合もコンパイルされます。 とはいえ、実行時に呼び出しも引数の評価も行わないため、 論理的には消えて無くなると考えて良いようです。