switchステートメントの拡張

C# 7では、switchステートメントのcase句に、値だけでなく、型を書けるようになりました。 型による条件の書き方は、前節のis演算子と同様です。 また、型による条件に加えて、when句というものを付けて追加の条件式を書くこともできます。

switch(変数)
{
    case  変数:
        // 型が一致しているときにここに来る
        // その型に変換した結果が変数に入っている
        break;
    case  変数 when 条件式:
        // 型が一致していて、かつ、条件式満たしているときにここに来る
        break;
    case :
        // 通常の値による条件との混在も可能
        break;
      ・
      ・
      ・
    default:
        // どの条件も満たさない時に実行される
        break;
}

例えば以下のような書き方ができます。

static void F(object obj)
{
    switch (obj)
    {
        case string s:
            Console.WriteLine("string #" + s.Length);
            break;
        case 7:
            Console.WriteLine("7の時だけここに来る");
            break;
        case int n when n > 0:
            Console.WriteLine("正の数の時にここに来る " + n);
            // ただし、上から順に判定するので、7 の時には来なくなる
            break;
        case int n:
            Console.WriteLine("整数の時にここに来る" + n);
            // 同上、0 以下の時にしか来ない
            break;
        default:
            Console.WriteLine("その他");
            break;
    }
}

上から逐次判定

C# 6までの、値による分岐しかなかったswitchステートメントとはちょっと違う部分があります。 以下の点に気を付けてください。

  • 条件の範囲が被る場合がある
    • 値による分岐の場合は、各 case がそれぞれ排他だった
    • 型による分岐が入ったことで、上記の例でいう 7intかつ正の数 ⊃ int のように、被りが起こり得る
  • 条件は上から順に判定する
    • 被りがない場合なら順序を気にする必要はなかった
      • なので、「ジャンプ テーブル化」(後述)という最適化手法が使えていた
    • 型による分岐を1つでも含むと、この前提が崩れて、ジャンプ テーブル化できない(逐次判定しかしない)

ジャンプ テーブル化の説明のために、以下のようなswitchを考えましょう。

switch(n)
{
    case 0: return "zero";
    case 1: return "one";
    case 2: return "two";
    case 3: return "three";
    case 4: return "four";
    case 5: return "five";
    case 6: return "six";
    case 7: return "seven";
    case 8: return "eight";
    case 9: return "nine";
    default: return "other";
}

こういうswitchであれば、以下のように、辞書を引いて結果を得ることもできるはずです。

var map = new Dictionary<int, string>
{
    { 0, "zero" },
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
    { 4, "four" },
    { 5, "five" },
    { 6, "six" },
    { 7, "seven" },
    { 8, "eight" },
    { 9, "nine" },
};

string s;
if (map.TryGetValue(n, out s)) return s;
else return "other";

caseの個数が少ないうちは普通に上から順に等値判定していく方が軽いんですが、 case数が増えれば増えるほど、辞書化した方が有利になります。

そこで、C# のswitchステートメント(というか、.NETの中間言語のswitch命令)では、caseの数が多い場合にこういう辞書を使った最適化を行うようになっています。 正確にいうと、辞書の値は条件分岐によるジャンプ先が入っていて、goto的な命令との組み合わせで実現されます。 そこで、「ジャンプ先のテーブルを引く」という意味で「ジャンプ テーブル化」と呼ばれます。

繰り返しになりますが、caseに型による条件を書いてしまうと、こういうジャンプ テーブル化ができなくなります。 というより、コンパイル結果的にはswitch命令が使えず、if-elseを繰り返すようなコードにコンパイルされます。 上から順に逐次判定になるので、case数があまりにも多いと実行性能的にあまりよくないので注意してください。

更新履歴

ブログ