プリプロセッサ

プリプロセッサとは

C#のコード上にはプログラムの実際の動作を書き、それをコンパイルして実行ファイルに変換します。
このコンパイルの直前に行う特定の処理をプリプロセッサ(プリプロセス命令)といいます。
(pre=前、process=処理)
プリプロセッサはコンパイラに特殊な指示を出すもので、実際のプログラムの動作には直接関係しませんが、上手く利用すれば生産性を向上させることができます。

プリプロセッサ用のキーワードは「#」記号から始まります。


#define TEST

//↑これがプリプロセッサ

using System;
using System.Collections.Generic;
//...

プリプロセッサ一覧

C#で用意されているプリプロセッサの一覧です。

  • #if
  • #else
  • #elif
  • #endif
  • #define
  • #undef
  • #warning
  • #error
  • #line
  • #region
  • #endregion
  • #pragma
  • #pragma warning
  • #pragma checksum

ここではよく使用されるプリプロセッサを説明します。

シンボルの定義

シンボルというのは後述する#if命令で使用するための、コンパイラ用の定数のようなものです。
ただし定数と言っても値はなく、シンボル名のみを持ちます。
シンボル定義は#define命令を使用します。


#define TEST

using System;
using System.Collections.Generic;
//...

上の例では「TEST」という名前のシンボルを定義しています。

#define命令はコードの先頭で定義する必要があります。
それ以外の場所で定義するとエラーとなります。

C言語やC++などでは、#defineはプログラム上で使用可能な定数を定義できますが、C#ではそのような機能はありません。

defineコンパイラオプション

#define命令と同じことはコンパイラオプションでも可能です。
VisualStudioならプロジェクトのプロパティから設定できます。

上部メニューから「(プロジェクト名)のプロパティ」を開きます。
プロジェクトのプロパティを開く

「ビルド」メニューを選択し、「条件付きコンパイル シンボル」欄にシンボル名を記述することで、#defineと同じ効果が得られます。
(「#」記号は必要ありません)
半角スペースで区切ることで複数同時に設定できます。
ビルドオプション

#define命令をソースコード上に記述する場合はそのソースファイル内でのみ有効となりますが、 コンパイラオプションでの設定は全てのコード上で有効になります。
また、コンパイラオプションでの設定はビルド構成ごとに設定可能です。

VisualStudioでは特殊なシンボル名として「DEBUG」と「TRACE」があらかじめ用意されており、チェックボックスでオン/オフができます。
その他、プログラムの実行環境によっていくつかのシンボルが定義済みになっています。
詳細は以下を参照してください。
#if プリプロセッサ ディレクティブ - C# リファレンス | Microsoft Docs

#undef

#undef命令は、#define命令で定義したシンボルを未定義にします。
この命令もコードの先頭で定義する必要があります。

コンパイラオプションで設定したシンボルを、そのソースコード上で無効にすることができます。


//DEBUGシンボルを無効
#undef DEBUG

using System;
using System.Collections.Generic;
//...

条件付きコンパイル

条件付きコンパイルは、シンボル定義に従ってコンパイル対象となるコードを切り替えることができます。
条件付きコンパイルには#if命令、#elif命令、#else命令、#endif命令を使用します。


#define TEST

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
#if TEST
            Console.WriteLine("#TEST is defined"); //この行のみが実行される
#elif TEST2
            Console.WriteLine("#TEST is not defined");
            Console.WriteLine("#TEST2 is defined");
#else
            Console.WriteLine("#TEST is not defined");
            Console.WriteLine("#TEST2 is not defined");
#endif
            Console.ReadLine();
        }
    }
}
#TEST is defined

動作としては通常のif文と同じです。
(「#elif」は「else if文」に相当します)
上記コードは「TEST」シンボルが定義されているため、16行目が実行されます。
#if命令が実行された場合は#elif、#elseは実行されません。
(コンパイル対象から除外されます)
#elif命令、#else命令は省略可能です。

コード先頭の#define命令を書き換えて、実行結果が切り替わることを確認してください。

条件付きコンパイルでは論理演算子とtrue/false、およびそれらとの比較のための等価演算子、グループ化演算子を使用できます。


#if DEBUG && TEST
    //DEBUGとTESTが定義されている
#endif


#if DEBUG || TEST
    //DEBUGかTESTが定義されている
#endif


#if !DEBUG
    //DEBUGが定義されていない
#endif


#if (DEBUG || TEST) && TEST2
    //DEBUGかTESTが定義され、TEST2が定義されている
#endif

#if true
    //無条件に有効
#endif


#if DEBUG == true
    //「#if DEBUG」と同等
#endif

条件付きコンパイルはデバッグビルドとリリースビルドの切り替えでよく使用されます。
VisualStudioの既定の設定では、デバッグビルド時には「DEBUG」というシンボルが定義されます。
これを利用して、開発時には「#if DEBUG」命令で開発に役立つメッセージなどを出力することができます。


static void Main(string[] args)
{
    //何らかの条件
    if (true)
    {
#if DEBUG
        Console.WriteLine("if文実行");
#endif
    }
    else
    {
#if DEBUG
        Console.WriteLine("else文実行");
#endif
    }

    Console.WriteLine("プログラムを終了します");
    Console.ReadLine();
}

開発時は逐一メッセージを確認できるようにしておき、完成後はリリースビルドに切り替えることで不要なメッセージをコンパイルに含めない、ということが簡単にできるようになります。
開発時のみに必要なクラスやメソッドを作った場合も、それら全体を#if命令で囲ってしまうことでリリースするプログラム上から除外し、軽量化することができます。

コードの領域の設定

VisualStudioでは標準でクラスやメソッド、名前空間などの単位でコードを折りたたむことができます。
コードの折り畳み

行数の多いメソッドなどは、普段は折りたたんでおくことでコードの見通しが良くなります。
#region命令、#endregion命令は、この折りたたむ範囲を自分で定義することができます。


static void Main(string[] args)
{
    //#region命令
    #region 変数宣言
    int num = 1;
    float real = 2.3f;
    string str = "abc";
    #endregion

    Console.WriteLine(num + real + str);
    Console.ReadLine();
}

#region~#endregionで挟まれた箇所が新たなコード領域となります。
#region命令には名前を付けることができます。
この名前は折りたたみ時に表示されます。
(省略も可能です)
#regionによるコードの折り畳み

#region命令の使用はメソッド内には限られないので、例えば関係する複数のメソッドを全て囲っておくなどの使用方法も可能です。


static void Main(string[] args)
{
    //
}

#region 関数群
static void MethodA()
{
    //
}

static void MethodB()
{
    //
}

static void MethodC()
{
    //
}
#endregion