まれに、プロジェクトのプロパティで定義したシンボルを、ソース・コード中で打ち消したい場合がある。例えば、Fig.19-6のようにMY_COMPILE_SWITCHというシンボルがプロジェクトのプロパティで定義されているとする。
このとき、ソース・コード内でこれを打ち消したい場合は、List 19-4のように記述する。
1: #undef MY_COMPILE_SWITCH
2: using System;
3:
4: namespace Sample004
5: {
6: class Class1
7: {
8: [STAThread]
9: static void Main(string[] args)
10: {
11: #if MY_COMPILE_SWITCH
12: Console.WriteLine("MY_COMPILE_SWITCH on");
13: #else
14: Console.WriteLine("MY_COMPILE_SWITCH off");
15: #endif
16: }
17: }
18: }
これを実行するとFig.19-7のようになる。
ここで注目すべきは1行目の#undefである。これは#defineの逆の効能をプリプロセッサに指示する。つまり、指定されたシンボルを定義されていない状態に戻してくれる。
ここまで読んで、ifステートメントがあれば、#ifはなくても困らないのではないか、と思った読者もいるだろう。そこで、#ifとifの機能の違いがはっきりと分かる例を示そう。List 19-5を見てほしい。
1: using System;
2:
3: namespace Sample005
4: {
5: class Class1
6: {
7: [STAThread]
8: static void Main(string[] args)
9: {
10: #if DEBUG
11: Console.WriteLine("Hello!");
12: #else
13: This is a pen!
14: #endif
15: }
16: }
17: }
List 19-5はデバッグ・ビルドでのみビルドでき、実行するとFig.19-8のようになる。
ここで注目していただきたいのは、13行目である。もちろん、13行目は、C#としては完全な文法違反である。このような行があれば、コンパイラはエラーを発してしまう。だが、このソース・コードは、デバッグ・ビルドを行うかぎり、エラーも出さないで正常に処理を終了する。これはプリプロセッサが、コンパイルする前に処理されるという役割を持っているためだ。プリプロセッサは、内容のいかんにかかわらず、#if‐#else‐#endifで指定された範囲を抽出してコンパイラ本体に渡す。そのため、選ばれなかった条件に該当する部分は、ただ単に無視され、どんな文法エラーが記述されていようと、それはなかったこととして扱われる。一方、ifステートメントの方は、条件が成立する場合も成立しない場合も、どちらもコンパイル処理されるので、常に条件が決まっている式を書いても、成立しない側の文法チェックが行われてしまう。
そのことから、プリプロセッサならではの使い方というものもある。例えば、ある環境ではコンパイル・エラーになるが、別の環境ではコンパイルできるソースというものが存在する場合がある。これはコンパイラのバージョン違いや、ライブラリの内容の相違などによって発生するものだ。こういう場合は、プリプロセッサで条件分けするようにしておけば、コンパイル・エラーになる部分をコンパイラ本体に処理させない、ということもできる。
#ifは、常にシンボル1個しか指定できないわけではない。より高度な指定の例を幾つか紹介しよう。
1: #define MY_SWITCH1
2: #undef MY_SWITCH2
3: using System;
4:
5: namespace Sample006
6: {
7: class Class1
8: {
9: [STAThread]
10: static void Main(string[] args)
11: {
12: #if true
13: Console.WriteLine("#if true");
14: #endif
15: #if false
16: Console.WriteLine("#if false");
17: #endif
18: #if MY_SWITCH1 || MY_SWITCH2
19: Console.WriteLine("MY_SWITCH1 or MY_SWITCH2 is ON");
20: #else
21: Console.WriteLine("MY_SWITCH1 or MY_SWITCH2 is OFF");
22: #endif
23: #if MY_SWITCH1 && MY_SWITCH2
24: Console.WriteLine("MY_SWITCH1 and MY_SWITCH2 is ON");
25: #else
26: Console.WriteLine("MY_SWITCH1 and MY_SWITCH2 is OFF");
27: #endif
28: }
29: }
30: }
これを実行するとFig.19-9のようになる。
12行目と15行目の条件に記述されたtrueとfalseは、それぞれ、条件が常に成立する、成立しない、ということを示す定数値である。条件が固定された#ifなど無意味と思われるかもしれないが、ソースを書いている途中、条件が決まらない段階で#ifによる場合分けを書く場合もある。そういう、仮に書き込んでおくという用途もある。
18行目と23行目は、複数のシンボルを参照した条件判断である。||と&&は、それぞれ、C#の演算子と同じ意味を持つ。||は、前後のシンボルのどちらかが定義されているとき、&&は前後のシンボルが両方とも定義されているときを意味する。このほかに、一致と不一致を判定する==と!=も使用できる。また、否定の!も使用できる。例えば、「#if MY_SYMBOL」ならシンボルが定義されているときだが、「#if !MY_SYMBOL」とすれば、シンボルが定義されていないとき、となる。
ここは、C/C++プログラマー向けの情報である。C/C++にもプリプロセッサがあり、同じような名前を用いるが、必ずしも機能は同じではない。C/C++では正しいが、C#では正しくない例をList 19-7に示す。
1: #define MY_SYMBOL1 123
2: #define MY_SYMBOL2(x) Console.WriteLine(x)
3: using System;
4:
5: #define MY_SYMBOL3
6:
7: #ifdef MY_SYMBOL4
8: #endif
9:
10: namespace Sample007
11: {
12: class Class1
13: {
14: [STAThread]
15: static void Main(string[] args)
16: {
17: Console.WriteLine("Not Executable");
18: }
19: }
20: }
まず1行目。C#では、シンボルは定義できるだけで、値を持たせることはできない。つまり、シンボルに「123」という文字列を関連付けることはできない。2行目のような引数も当然存在しない。5行目は一見正しいように見えるが、C#では#defineや#undefは、コンパイラ本体が処理するすべてのコードの前に記述される必要がある。このソースでは、3行目のusingステートメントが最初のコードなので、それよりも前に記述しなければならない。7行目は、C/C++プログラマーがうっかりミスをしそうな例だが、#ifdefはC#には存在しないので#ifを使う。
Copyright© Digital Advantage Corp. All Rights Reserved.