閉じる

【C#】switch式の書き方徹底解説!switch文との違いやパターンマッチングも

C#のプログラミングにおいて、条件分岐は避けては通れない基本的な要素です。

従来のswitch文は多くの場面で利用されてきましたが、C# 8.0で導入された「switch式」は、より簡潔で読みやすいコードを記述するための強力な武器となりました。

本記事では、最新のC#におけるswitch式の書き方から、高度なパターンマッチング、従来のswitch文との決定的な違いまでを徹底的に解説します。

switch式の基本概念と構文

C# 8.0以降、条件分岐を「式」として記述できるようになったことは、開発効率を大きく向上させました。

まずは、その基本的な書き方と構造を理解しましょう。

switch式の基本構造

switch式は、値を評価してその結果を直接返すことができます。

従来のswitch文との最大の違いは、各ケースが値を返す「腕(アーム)」として構成される点にあります。

C#
using System;

class Program
{
    static void Main()
    {
        int dayOfWeek = 3;

        // switch式を用いた値の代入
        string dayName = dayOfWeek switch
        {
            1 => "Monday",
            2 => "Tuesday",
            3 => "Wednesday",
            4 => "Thursday",
            5 => "Friday",
            6 => "Saturday",
            7 => "Sunday",
            _ => "Unknown" // デフォルトケース(破棄)
        };

        Console.WriteLine($"Today is {dayName}.");
    }
}
実行結果
Today is Wednesday.

従来のswitch文との主な書き方の違い

switch式では、caseキーワードやbreakキーワードを記述する必要がありません。

代わりに=>(ラムダ演算子に似た記法)を使用します。

特徴switch文switch式
記述形式ステートメント(命令文)式(値を返す)
キーワードcase, break, default不要(記号で代用)
デフォルト値default:_ (アンダースコア)
代入方法変数に値を代入する処理を書く式そのものを代入可能

switch式は、すべてのパスが値を返す必要があります。

そのため、網羅性を確保するために「_」(ディスカード/破棄パターン)を最後に追加することが一般的です。

これがない場合、どのパターンにも一致しない値が入力されると実行時に例外が発生する可能性があります。

switch式の高度なパターンマッチング

switch式の真価は、単なる値の比較だけでなく、「パターンマッチング」と組み合わせたときに発揮されます。

これにより、複雑なオブジェクトの状態に基づいた分岐を極めてシンプルに記述できます。

プロパティパターンによるオブジェクトの判定

オブジェクトの特定のプロパティの値を直接チェックして分岐させることができます。

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

C#
public class Order
{
    public int ItemsCount { get; set; }
    public decimal TotalPrice { get; set; }
}

public class DiscountCalculator
{
    public decimal GetDiscount(Order order) => order switch
    {
        // プロパティItemsCountが10以上、かつTotalPriceが10000以上の場合
        { ItemsCount: >= 10, TotalPrice: >= 10000 } => 0.2m,
        // ItemsCountが5以上の場合
        { ItemsCount: >= 5 } => 0.1m,
        // それ以外
        _ => 0.0m
    };
}

この例では、Orderオブジェクトのプロパティを直接波括弧{}内で指定しています。

複数の条件を組み合わせて、非常に宣言的にロジックを記述できていることがわかります。

位置パターン(タプルパターン)の活用

複数の変数を一つのセットとして評価したい場合、タプルを用いた位置パターンが非常に有効です。

C#
string GetState(bool isLoggedIn, bool hasPermission) => (isLoggedIn, hasPermission) switch
{
    (true, true) => "管理者としてログイン中",
    (true, false) => "一般ユーザーとしてログイン中",
    (false, _) => "未ログイン状態",
};

実行結果(例: true, falseを渡した場合)

実行結果
一般ユーザーとしてログイン中

この書き方では、if文をネストさせる必要がなくなり、「状態の組み合わせ」をマトリックスのように一覧で管理できるため、可読性が劇的に向上します。

C# 9.0以降の強力なパターン拡張

C# 9.0以降、switch式はさらに進化しました。

関係パターン論理パターンの導入により、数値の範囲指定などが直感的に行えるようになっています。

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

従来のswitch文では「60点以上80点未満」といった範囲指定を記述するのは困難でしたが、現在のswitch式では以下のように記述可能です。

C#
string EvaluateScore(int score) => score switch
{
    >= 90 => "秀",
    >= 80 and < 90 => "優",
    >= 70 and < 80 => "良",
    >= 60 and < 70 => "可",
    < 60 => "不可",
    _ => "測定不能"
};

ここで使われているandornotは、従来の&&||とは異なり、パターンの一部として機能する新しいキーワードです。

notパターンの活用

特定の型ではない、あるいは特定の値ではない場合に合致するnotパターンも非常に便利です。

C#
bool IsNotNull(object? obj) => obj switch
{
    not null => true,
    null => false
};

このように、nullチェックも式として美しく記述できます。

when句によるガード条件の指定

パターンマッチングだけでは表現しきれない複雑な条件がある場合、when句(ガード条件)を使用します。

when句の具体的な使い方

when句を使用すると、パターンが一致した後にさらなる真偽値によるフィルタリングをかけることができます。

C#
public record User(string Name, bool IsActive, int Level);

string GetUserStatus(User user) => user switch
{
    { Level: >= 100 } when user.IsActive => "アクティブなレジェンドユーザー",
    { Level: >= 100 } when !user.IsActive => "休眠中のレジェンドユーザー",
    _ => "一般ユーザー"
};

when句の中では、対象のオブジェクトのプロパティを自由に参照して、任意の論理式を記述できます。

これにより、パターンの簡潔さを保ちつつ、複雑なビジネスロジックを組み込むことが可能になります。

switch式とswitch文の使い分けガイド

switch式は万能に見えますが、従来のswitch文の方が適している場面もあります。

どちらを採用すべきか、判断基準を明確にしましょう。

switch式を選ぶべきケース

値を返すことが目的である場合

変数への代入や、メソッドの戻り値を決定する際には、switch式が圧倒的にクリーンです。

ロジックが宣言的である場合

「この条件の時はこの値」というマッピングに近い処理には最適です。

パターンマッチングを多用する場合

型やプロパティに基づく複雑な分岐は、switch式の得意分野です。

switch文を選ぶべきケース

複数の副作用や長い処理がある場合

複数の副作用(メソッド呼び出しなど)が伴う場合や、一つのケース内で複数のメソッドを呼んだり長い処理を書く必要がある場合は、switch文の方が適している。

switch式のアームに複雑なブロックは書けず、基本的に一つの式である必要があるため。

フォールスルーや複数caseを同じ処理にしたい場合

フォールスルー(caseをまたぐ処理)が必要な場合や、複数のcaseを並べて同じ処理をさせたい場合は、C#では限定的ながらswitch文の方が記述しやすいことがある。

switch式ではorパターンで対応可能だが、文の方が直感的な場合もある。

実装例の比較

網羅性のチェックと例外処理

switch式を扱う上で最も注意すべき点は、「すべての入力に対して値を返す必要がある」という点です。

SwitchExpressionExceptionの回避

もしswitch式でどのパターンにも合致しない値が渡された場合、System.Runtime.CompilerServices.SwitchExpressionExceptionが発生します。

これを防ぐためには、常に_(ワイルドカード)によるデフォルトパターンを用意しておくことが推奨されます。

C#
int input = 10;
string result = input switch
{
    1 => "One",
    2 => "Two",
    _ => throw new ArgumentOutOfRangeException(nameof(input), "1か2以外は許可されていません")
};

このように、_を使用して明示的に例外を投げることも可能です。

これにより、プログラムのバグを早期に発見できる堅牢なコードになります。

列挙型(enum)との組み合わせ

列挙型をswitch式で扱う場合、Visual StudioなどのIDEは「すべての列挙値がカバーされているか」をチェックしてくれます。

新しい列挙値が追加された際に、switch式を更新し忘れると警告を出してくれるため、保守性が非常に高くなります。

switch式をより読みやすくするためのテクニック

最後に、switch式を実務で使う際の「美しく書くためのコツ」を紹介します。

1. タプルを活用した多次元分岐

「ログイン状態」×「権限」×「プラン」といった3つ以上の要素を組み合わせる場合、タプルを使うとネストを完全に排除できます。

C#
var result = (user.Status, user.Role, subscription.Type) switch
{
    (Status.Active, Role.Admin, _) => "フルアクセス権限",
    (Status.Active, Role.User, Plan.Premium) => "プレミアムアクセス",
    (Status.Active, Role.User, Plan.Free) => "限定アクセス",
    (Status.Banned, _, _) => "アクセス禁止",
    _ => "ゲストアクセス"
};

2. ローカル関数との組み合わせ

switch式のアームが長くなりそうな場合は、その部分をローカル関数に切り出すことで、全体の視認性を保つことができます。

C#
string Process(Data data) => data switch
{
    { Type: DataType.Image } => ProcessImage(data),
    { Type: DataType.Video } => ProcessVideo(data),
    _ => "Unknown Format"
};

// switch式の外(またはローカル関数)で詳細を定義
string ProcessImage(Data d) => $"Processing image: {d.Name}";
string ProcessVideo(Data d) => $"Processing video: {d.Name}";

このように、「分岐の構造」と「個別の処理内容」を分離することで、コードの意図がより明確になります。

まとめ

C#のswitch式は、単なるswitch文の代替ではなく、「データから結果を導き出す」という関数型の思考をC#に持ち込んだ画期的な機能です。

パターンマッチングや関係パターン、論理演算子を組み合わせることで、これまでif-elseの迷宮だった複雑なロジックを、驚くほどシンプルに整理できます。

まずは単純な値の変換からswitch式を取り入れ、徐々に高度なパターンマッチングを活用して、美しくメンテナンス性の高いC#コードを目指しましょう。

最新のC#機能を使いこなすことは、開発スピードの向上だけでなく、バグの少ない堅牢なシステム構築への第一歩となります。

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

URLをコピーしました!