閉じる

C#のswitch式完全ガイド|モダンな記法とパターンマッチングの活用法

C#という言語は、バージョンアップを重ねるごとに洗練され、より簡潔で読みやすいコードが書けるように進化してきました。

その中でも、C# 8.0で導入され、その後のバージョンで強力に強化された「switch式」は、従来のswitch文を置き換えるモダンな記法の代表格です。

従来のswitch文は、手続き的な記述が中心であり、コードが冗長になりがちでした。

しかし、switch式やパターンマッチングを駆使することで、宣言的で直感的なコードを実現できます。

本記事では、switch式の基本から、実戦で役立つ高度なパターンマッチングの活用法までを詳しく解説します。

最新のC#標準に合わせた、最も効率的なコーディングスタイルを身につけていきましょう。

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

まずは、従来のswitch文とモダンなswitch式の根本的な違いを理解しましょう。

switch式は単なる「短縮記法」ではなく、「値を返す式」であるという点が最大の鍵となります。

文(Statement)から式(Expression)への進化

従来のswitch文は、処理の流れを制御するための「文」でした。

そのため、各ケースの中で変数に値を代入し、最後に break で抜けるという手順が必要でした。

一方、switch式はそれ自体が評価結果を持つ「式」です。

以下のサンプルコードで、その構造の違いを確認してみましょう。

C#
using System;

public class Program
{
    public static void Main()
    {
        // 判定対象の列挙型
        DayOfWeek day = DateTime.Now.DayOfWeek;

        // --- 従来のswitch文 ---
        string messageOld;
        switch (day)
        {
            case DayOfWeek.Saturday:
            case DayOfWeek.Sunday:
                messageOld = "休日はリラックスしましょう。";
                break;
            default:
                messageOld = "仕事に集中しましょう。";
                break;
        }

        // --- モダンなswitch式 ---
        // 式なので、そのまま変数に代入できる
        string messageNew = day switch
        {
            DayOfWeek.Saturday or DayOfWeek.Sunday => "休日はリラックスしましょう。",
            _ => "仕事に集中しましょう。" // _ はデフォルト(破棄)を意味する
        };

        Console.WriteLine($"文の結果: {messageOld}");
        Console.WriteLine($"式の結果: {messageNew}");
    }
}
実行結果
文の結果: 仕事に集中しましょう。
式の結果: 仕事に集中しましょう。

switch式の主な特徴

switch式には、従来のswitch文にはなかったいくつかの重要な特徴があります。

特徴詳細
キーワードの簡略化casebreak を使用せず、=> を使用します。
デフォルトの扱いdefault の代わりにアンダースコア _ (破棄パターン)を使用します。
網羅性のチェックすべてのパターンが網羅されていない場合、コンパイラが警告を出してくれるため、バグを防げます。
式の位置変数の右側(代入)や、メソッドの戻り値として直接記述できます。

これらの特徴により、コードの視認性が大幅に向上し、ロジックの本質に集中できるようになります。

パターンマッチングの活用

switch式の真価は、強力な「パターンマッチング」と組み合わせた時に発揮されます。

単なる値の比較だけでなく、オブジェクトの型やプロパティの状態に基づいて分岐させることが可能です。

型パターンとプロパティパターン

オブジェクトが特定の型であるかを確認し、さらにその内部のプロパティを条件に含めることができます。

これを「プロパティパターン」と呼びます。

C#
public record Order(int Amount, bool IsExpress);

public static string GetShippingStatus(object obj) => obj switch
{
    // 型がOrderで、かつAmountが10000以上の場合
    Order { Amount: >= 10000 } => "高額注文のため、送料無料かつ優先配送です。",
    
    // 型がOrderで、かつIsExpressがtrueの場合
    Order { IsExpress: true } => "速達便で配送準備中です。",
    
    // 型がOrderであれば何でも一致
    Order => "通常配送の準備中です。",
    
    // それ以外の型(nullを含む)
    null => "データが存在しません。",
    _ => "不明なデータ型です。"
};

この記法では、型チェックとプロパティへのアクセス、そして条件判定を一行に凝縮して記述できるのが強みです。

タプルパターン

複数の値を組み合わせて一度に判定したい場合には、「タプルパターン」が非常に有効です。

例えば、ゲームのステータス管理や、複数の入力パラメータに基づくロジックなどで活躍します。

C#
public static string GetGameResult(string player1, string player2) => (player1, player2) switch
{
    ("グー", "チョキ") or ("チョキ", "パー") or ("パー", "グー") => "プレイヤー1の勝ち!",
    ("チョキ", "グー") or ("パー", "チョキ") or ("グー", "パー") => "プレイヤー2の勝ち!",
    _ when player1 == player2 => "引き分けです。",
    _ => "無効な手です。"
};

タプルパターンを使用することで、if 文を何層もネストさせる必要がなくなり、「マトリックス状の条件」をそのままコードに落とし込めます

C# 9.0以降の強化:論理パターンとリレーショナルパターン

C# 9.0以降、パターンマッチングはさらに進化し、andornot といった論理演算子や、数値の比較演算子が直感的に使えるようになりました。

リレーショナルパターンによる範囲判定

従来のswitch文では、数値の範囲判定を行うには when 句を多用するか、複雑な if-else を書く必要がありました。

最新のswitch式では、以下のようにスマートに記述できます。

C#
public static string EvaluateScore(int score) => score switch
{
    // リレーショナルパターン(比較演算子)
    < 0 or > 100 => "スコアが範囲外です。",
    >= 90 => "最高評価(S)です!",
    >= 80 => "優秀な成績(A)です。",
    >= 60 => "合格点(B)です。",
    _ => "不合格(C)です。次は頑張りましょう。"
};

not パターンの活用

特定の条件「ではない」ことを示したい場合、not パターンが便利です。

特に null チェックとの相性は抜群です。

C#
public static void PrintMessage(object? input)
{
    // inputがnullではない場合
    if (input is not null)
    {
        string result = input switch
        {
            string s and { Length: > 0 } => $"文字列を受信: {s}",
            int i => $"数値を入力: {i}",
            _ => "不明な形式です。"
        };
        Console.WriteLine(result);
    }
}

このように、自然言語に近い形でロジックを記述できるため、開発者の意図が伝わりやすくなります。

高度なテクニック:ガード句とネスト

複雑な条件が必要な場合、パターンマッチングの後に when 句を付け加えることで、さらなる条件(ガード句)を設定できます。

guard句(when)の利用

パターンの型や構造だけでは表現しきれない詳細なロジックを記述する際に使用します。

C#
public record User(string Name, int Age, bool IsPremium);

public static string GetDiscountMessage(User user) => user switch
{
    // プレミアム会員、または65歳以上のユーザーに適用
    User { IsPremium: true } => "プレミアム会員特典により、全品20%OFFです。",
    User { Age: var age } when age >= 65 => "シニア割引により、全品15%OFFです。",
    
    // 名前が特定の文字で始まる場合などの特殊な条件
    User { Name: var name } when name.StartsWith("Admin") => "管理者専用メニューを表示します。",
    
    _ => "通常価格でのご案内となります。"
};

var age のように、パターン内で変数宣言を行うことで、その後の when 句や右辺の式でその値を利用できるのも便利なポイントです。

ネストされたswitch式

複雑な階層構造を持つデータを処理する場合、switch式の中にさらにswitch式を記述することも可能です。

ただし、可読性を損なわないよう注意が必要です。

C#
public enum Category { Food, Electronics }
public record Product(string Name, Category Category, int Price);

public static string GetTaxRate(Product product) => product.Category switch
{
    Category.Food => product.Price switch
    {
        < 1000 => "軽減税率8%(低価格食品)",
        _ => "標準税率10%(高級食品)"
    },
    Category.Electronics => "標準税率10%",
    _ => throw new ArgumentException("無効なカテゴリです。")
};

網羅性の保証とデフォルトケース

switch式を使用する際、最も強力な武器の一つが「網羅性のチェック(Exhaustiveness check)」です。

コンパイラによるエラー検出

enum(列挙型)を対象にしたswitch式で、すべての値をカバーしていない場合、C#コンパイラは警告(または設定によりエラー)を出します。

C#
public enum Status { Todo, Doing, Done, OnHold }

public static string GetLabel(Status status) => status switch
{
    Status.Todo => "未着手",
    Status.Doing => "進行中",
    Status.Done => "完了",
    // Status.OnHold が漏れていると、コンパイラが警告を出す
};

もし将来的に Status に新しい値(例:Archived)が追加された場合、switch式を使っていれば即座に修正が必要な箇所が見つかります。

これは保守性を高める上で極めて重要なメリットです。

破棄パターン _ の適切な使い道

_ は「何にでも一致する」パターンです。

しかし、何でもかんでも _ で受けてしまうと、予期せぬ値が入ってきた際の問題を見逃す可能性があります。

明確な例外を投げたい場合は、以下のように記述するのがモダンな作法です。

C#
public static string GetSafeLabel(Status status) => status switch
{
    Status.Todo => "未着手",
    Status.Doing => "進行中",
    Status.Done => "完了",
    Status.OnHold => "一時停止",
    _ => throw new InvalidOperationException($"未定義のステータスです: {status}")
};

パフォーマンスと注意点

switch式は非常に高速ですが、いくつかの注意点もあります。

パフォーマンス

内部的には、switch式は効率的なジャンプテーブルや、一連の型チェックにコンパイルされます。

従来の if-else を並べるよりも、多くの場合で同等か、それ以上のパフォーマンスを発揮します。

特に多数のケースがある場合は、ハッシュテーブルのような最適化が行われることもあります。

可読性のバランス

switch式は強力ですが、1つの式に複雑なロジックを詰め込みすぎると、かえってコードが読みにくくなる「詰め込みすぎ」の罠に陥ることがあります。

  • 1つのケース(=>の右側)が複数行にわたる場合は、メソッドとして抽出することを検討してください。
  • ネストが2段階を超える場合は、ロジック自体の見直しが必要です。
良い例避けるべき例
短い1行の変換ロジック右辺に巨大なラムダ式や複雑な計算式がある
明確なパターンマッチング条件分岐のために無理やりタプルを使いすぎる
網羅性が保証されている常に _ で逃げてしまい、不正な値を無視している

まとめ

C#のswitch式は、コードをより安全で、簡潔に、そして美しくするための強力なツールです。

従来のswitch文と比較して、「値を返す」という性質「パターンマッチング」との融合により、複雑な分岐条件を驚くほどシンプルに記述できるようになりました。

プロパティパターンやタプルパターン、そして論理演算子を活用することで、バグの入り込む隙を減らし、意図が明確なコードを書くことができます。

これからC#で新しいコードを書く際は、ぜひ従来のswitch文ではなく、最新のswitch式を第一選択として活用してみてください。

モダンなC#の機能を使いこなすことで、開発の生産性とコードの品質は確実に向上するはずです。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!