C#という言語は、バージョンを重ねるごとに表現力が飛躍的に向上してきました。

その中でも、C# 8.0で導入された「switch式」は、従来のswitch文が抱えていた冗長さを解消し、モダンなプログラミングスタイルを実現するための極めて重要な機能です。

単なる構文の書き換えにとどまらず、関数型プログラミングのエッセンスを取り入れたこの機能は、コードの読みやすさと保守性を劇的に高めてくれます。

本記事では、switch式の基本的な書き方から、C# 9.0以降で強化されたパターンマッチングの高度な活用テクニック、さらには実務で役立つ実践的なTipsまでを徹底的に解説します。

命令的な記述から宣言的な記述へとシフトすることで、バグの入り込みにくい、洗練されたコードを目指しましょう。

switch式の基本概念と従来のswitch文との違い

従来のswitch文は「文(Statement)」であり、条件に応じて特定の処理ブロックを実行する役割を担っていました。

一方、switch式は「式(Expression)」であり、評価された結果として一つの値を返すことが最大の特徴です。

まずは、従来のswitch文とswitch式の記述方法を比較してみましょう。

switch文とswitch式の比較

以下は、曜日を表す列挙型から、その日が平日か休日かを判定するコードの例です。

C#
using System;

public enum DayOfWeek { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }

public class Program
{
    public static void Main()
    {
        DayOfWeek day = DayOfWeek.Saturday;

        // 従来のswitch文
        string resultStatement;
        switch (day)
        {
            case DayOfWeek.Saturday:
            case DayOfWeek.Sunday:
                resultStatement = "休日";
                break;
            default:
                resultStatement = "平日";
                break;
        }

        // 最新のswitch式
        string resultExpression = day switch
        {
            DayOfWeek.Saturday or DayOfWeek.Sunday => "休日",
            _ => "平日"
        };

        Console.WriteLine($"switch文の結果: {resultStatement}");
        Console.WriteLine($"switch式の結果: {resultExpression}");
    }
}
実行結果
switch文の結果: 休日
switch式の結果: 休日

switch式の主な変更点

上記のコードからわかる通り、switch式にはいくつかの重要な文法的ルールがあります。

  1. 対象となる変数が前方に来る: day switch { ... } という形式で記述します。
  2. caseキーワードとコロンが不要: 代わりにラムダ式のような => (ラムダ矢印)を使用します。
  3. breakキーワードが不要: 式であるため、値を返した時点で終了します。
  4. デフォルトケースはアンダースコア(_): 「破棄(discard)」と呼ばれる記法を使い、どのパターンにも一致しない場合を指定します。
  5. セミコロンの位置: 式の最後(閉じ括弧の後)にセミコロンが必要です。

多彩なパターンマッチングの活用

switch式の真価は、強力なパターンマッチングと組み合わせることで発揮されます。

単なる値の比較だけでなく、型の判定、プロパティの状態、数値の範囲など、複雑な条件を簡潔に表現できます。

プロパティパターン

オブジェクトの特定のプロパティの状態に基づいて分岐させたい場合、プロパティパターンが便利です。

クラスや構造体の中身を直接参照して条件を指定できます。

C#
public class Order
{
    public decimal TotalAmount { get; set; }
    public bool IsPremiumMember { get; set; }
}

public class DiscountCalculator
{
    public decimal CalculateDiscount(Order order) => order switch
    {
        { IsPremiumMember: true, TotalAmount: > 10000 } => 2000,
        { IsPremiumMember: true } => 1000,
        { TotalAmount: > 10000 } => 500,
        _ => 0
    };
}

この例では、Order オブジェクトのプロパティを抽出し、それらが特定の条件を満たすかどうかを一行で判定しています。

関係パターンと論理パターン

C# 9.0以降では、比較演算子(<, >, <=, >=)を使用した関係パターンや、and, or, not を使用した論理パターンが利用可能になりました。

これにより、数値範囲の判定が驚くほど直感的になります。

C#
public static string GetAgeCategory(int age) => age switch
{
    < 0 => "無効な年齢",
    <= 12 => "子供",
    >= 13 and <= 19 => "ティーンエイジャー",
    < 65 => "成人",
    _ => "シニア"
};

従来のif-else文の連続に比べて、「何に対して判定を行っているか」が明確になり、ロジックの見通しが良くなります。

タプルパターン

複数の変数を組み合わせて判定を行いたい場合は、タプルパターンを使用します。

これは「状態遷移」などを記述する際に非常に強力です。

C#
public string GetAction(string state, string transition) => (state, transition) switch
{
    ("Opened", "Close") => "Closing the door",
    ("Closed", "Open")  => "Opening the door",
    ("Closed", "Lock")  => "Locking the door",
    (_, "Reset")        => "Resetting system",
    _                   => "Invalid transition"
};

複数の入力値を一つのタプルとしてまとめ、それらの組み合わせをパターンとして定義することで、複雑な条件分岐の網羅性が向上します。

高度なテクニック:ガード句と型パターン

さらに複雑な条件が必要な場合、パターンの後に when キーワードを付け加えるガード句を使用できます。

また、実行時の型を判定してキャストする型パターンも、switch式の得意分野です。

型パターンの利用

オブジェクトの型に応じて処理を分ける際、以前は asis によるキャストが必要でしたが、switch式ならスマートに記述できます。

C#
public abstract class Shape { }
public class Circle : Shape { public double Radius { get; set; } }
public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } }

public static double CalculateArea(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle r => r.Width * r.Height,
    null => throw new ArgumentNullException(nameof(shape)),
    _ => throw new NotSupportedException("未知の図形です")
};

型判定と同時に変数への代入(この例では cr)が行われるため、スコープ内ですぐにプロパティへアクセスできるのが利点です。

ガード句による詳細なフィルタリング

パターンの条件だけでは不足する場合、when を使って任意の論理式を追加できます。

C#
public static string CategorizeWeight(double bmi) => bmi switch
{
    double n when n < 18.5 => "低体重",
    double n when n < 25.0 => "普通体重",
    double n when n < 30.0 => "肥満(1度)",
    _ => "肥満(2度以上)"
};

ただし、C# 9.0以降は先述の関係パターン(< 18.5 など)が使えるため、単純な比較であれば関係パターンを優先し、外部メソッドの呼び出しが必要な場合などに when を活用するのがベストプラクティスです。

リストパターンによる配列・リストの判定

C# 11から導入されたリストパターンにより、配列やリストの要素数や中身に基づいた条件分岐が可能になりました。

これはデータのパース処理などで非常に役立ちます。

C#
public static string AnalyzeNumbers(int[] numbers) => numbers switch
{
    [] => "空のリスト",
    [0, ..] => "0から始まるリスト",
    [var first, var second] => $"要素数は2つ: {first}, {second}",
    [.., var last] when last > 10 => $"最後の要素 {last} が10より大きい",
    _ => "その他のリスト"
};
記法意味
[]空のコレクションにマッチ
[1, 2, 3]指定した値と完全に一致する3要素のコレクションにマッチ
[var x, ..]1つ以上の要素を持ち、最初の要素をxに代入。残りは無視
[.., var last]1つ以上の要素を持ち、最後の要素をlastに代入

.. (スプレッド要素)を使用することで、任意個数の要素をスキップできるのが特徴です。

switch式を導入するメリット

switch式の活用は、単なるコードの短縮以上のメリットをもたらします。

1. 網羅性のチェック(Exhaustiveness Check)

switch式は値を返す必要があるため、コンパイラが「すべてのパターンが網羅されているか」を厳格にチェックします。

列挙型(enum)を使用している場合、新しい値を定義したのにswitch式に追加し忘れていると、コンパイル警告(CS8509)が発生します。

これにより、実行時に予期しない値が入ってきてロジックが破綻するのを防ぐことができます。

2. 不変性(Immutability)の促進

従来のswitch文では、変数を宣言した後に、各ケースの中で値を代入(再代入)する形になりがちでした。

C#
// switch文(再代入が必要)
string message;
switch (status) { ... message = "OK"; ... }

// switch式(初期化と同時に代入)
string message = status switch { ... };

switch式を使えば、読み取り専用(readonly)の変数に対して直接結果を代入できるため、意図しない書き換えを防ぎ、安全なプログラムを書くことができます。

3. 可読性と保守性の向上

ネストされたif文や冗長なcaseラベルが排除されることで、コードの意図が明確になります。

条件と結果が一対一で対応する「表」のような形式で記述できるため、ロジックの修正や追加が容易になります。

パフォーマンスへの影響

「switch式はswitch文より遅いのではないか?」という懸念を持たれることがありますが、結論から言えば、パフォーマンス上の不利はほとんどありません

多くの場合、コンパイラはswitch式を従来のswitch文や効率的なジャンプテーブル、あるいは最適化された型判定ルーチンに変換します。

むしろ、人間が複雑なif-elseを書くよりも、コンパイラの最適化が効きやすい構造になることも少なくありません。

ただし、非常に複雑なパターン(巨大なタプルや深い再帰パターン)を頻繁に評価する場合は、ベンチマークを取る価値はありますが、一般的なビジネスロジックにおいて性能低下を心配する必要はありません。

実践的な使い分けと注意点

非常に便利なswitch式ですが、何でもかんでもswitch式にすれば良いというわけではありません。

switch文を使うべきケース

値を返すことが目的ではない場合

各ケースで複雑な手続き(ログ出力、ファイル書き込み、データベース操作など)を複数行実行する必要がある場合は、従来のswitch文の方が適しています。

非同期処理を含む場合

現在、switch式の各アーム内でawaitを直接使用することはできません。

非同期メソッドの結果を待ちたい場合は、switch文を使用するか、別の工夫(例: 非同期処理を事前に実行して結果をスイッチする等)が必要です。

switch式を最大限活かすためのコツ

式本体のメンバ(Expression-bodied members)と組み合わせる

メソッド全体を式形式で記述し、switch式をそのまま返すことで非常に簡潔になります。

例: public string GetName(int id) => id switch { 0 => "Zero", 1 => "One", _ => "Unknown" };

例外を投げる

予期しない値が来た場合は破棄パターン(_)で例外を投げることで防御的プログラミングを徹底できます。

例: ... switch { A => ..., B => ..., _ => throw new InvalidOperationException() }

まとめ

C#のswitch式は、コードの冗長性を削ぎ落とし、宣言的で美しいロジックを記述するための強力な武器です。

従来のswitch文と比較して、「値を返すことに特化している」「パターンマッチングとの親和性が極めて高い」「コンパイラによる網羅性チェックが働く」といった多くの利点があります。

特にC# 9.0の関係パターンやC# 11のリストパターンを組み合わせることで、これまでif-elseの迷宮になりがちだった複雑な条件分岐を、シンプルかつ堅牢に整理することができます。

モダンなC#開発において、switch式の習得は必須と言っても過言ではありません。

まずは日常的なコードの中で、単純な値の変換や列挙型の判定からswitch式に置き換えてみてください。

コードの「ノイズ」が減り、本質的なロジックが浮き彫りになる感覚を実感できるはずです。

より安全で、読みやすく、そして書いていて楽しいC#コーディングのために、ぜひ今回紹介したテクニックを自身のプロジェクトに取り入れてみてください。