C#は、バージョンを重ねるごとに表現力が飛躍的に向上してきました。
その中でも、特にコードの可読性と保守性を劇的に変えたのがパターンマッチングとswitch式の導入です。
従来のswitch文は、特定の値との一致を確認するだけの単純な制御構文でしたが、現代のC#におけるswitchは、複雑なオブジェクトの構造や状態を直感的に判定するための強力なツールへと進化しています。
この記事では、パターンマッチングの基本から、実務で役立つ応用的なswitch式の書き方まで、具体的なサンプルコードを交えて詳しく解説します。
冗長なif-elseの羅列を卒業し、モダンで洗練されたC#コードを書くためのテクニックを身に付けていきましょう。
switch文からswitch式への進化
C# 8.0以降、従来の「文(Statement)」としてのswitchに加えて、「式(Expression)」としてのswitchが導入されました。
これにより、値を判定してその結果を直接変数に代入したり、メソッドの戻り値として返したりすることが非常に簡潔に記述できるようになりました。

従来のswitch文とswitch式の比較
まずは、従来のswitch文と、モダンなswitch式の書き方の違いを見てみましょう。
using System;
public class Program
{
public static void Main()
{
int dayOfWeek = 3;
// --- 従来のswitch文 ---
string messageOld;
switch (dayOfWeek)
{
case 1:
messageOld = "月曜日";
break;
case 7:
messageOld = "日曜日";
break;
default:
messageOld = "平日";
break;
}
Console.WriteLine($"従来: {messageOld}");
// --- モダンなswitch式 ---
// 直接変数に代入可能。ラムダ式のような「=>」を使用する
string messageNew = dayOfWeek switch
{
1 => "月曜日",
7 => "日曜日",
_ => "平日" // 従来のdefaultに相当する「破棄パターン」
};
Console.WriteLine($"モダン: {messageNew}");
}
}
従来: 平日
モダン: 平日
switch式の最大の特徴は、式全体が値を返す点にあります。
そのため、caseやbreakといったキーワードが不要になり、セミコロンで区切る必要もありません。
また、全てのパターンを網羅していない場合にコンパイラが警告を出してくれるため、バグの防止にも繋がります。
パターンマッチングの基本バリエーション
パターンマッチングとは、「ある値が特定の形状(型や値、状態)に一致するかどうか」を調べる仕組みです。
これをswitch式と組み合わせることで、非常に強力な条件分岐を実現できます。
型パターン(Type Pattern)
オブジェクトの「型」を判定し、同時にその型として変数にキャストして扱う手法です。

using System;
public class Program
{
public static void Main()
{
object data = "Hello, C#";
string result = data switch
{
int i => $"数値です: {i * 10}", // int型ならiとして扱う
string s => $"文字列です: {s.Length}文字", // string型ならsとして扱う
null => "データが空です",
_ => "未知の型です"
};
Console.WriteLine(result);
}
}
文字列です: 9文字
従来のコードでは、is演算子で型をチェックした後に明示的なキャストを行う必要がありましたが、型パターンを使用すれば、判定とキャストを一行で同時に行えるため、コードが非常にスッキリします。
プロパティパターン(Property Pattern)
オブジェクトが持つ特定のプロパティの値を基準にマッチングを行います。
これは、クラスや構造体の状態に応じて処理を分けたい場合に非常に便利です。

using System;
public record Order(int Amount, string Country, bool IsExpedited);
public class Program
{
public static void Main()
{
var myOrder = new Order(5000, "Japan", true);
// プロパティの状態を{}内で指定する
string shippingStatus = myOrder switch
{
{ Country: "Japan", IsExpedited: true } => "国内特急便",
{ Country: "Japan" } => "国内通常便",
{ Amount: > 10000 } => "海外高額配送(送料無料)",
_ => "通常配送"
};
Console.WriteLine($"配送区分: {shippingStatus}");
}
}
配送区分: 国内特急便
C# 10以降では、{ Customer.Address.City: "Tokyo" }のように、ネストされたプロパティを直接参照することも可能になりました。
これにより、深い階層にあるデータに基づいた分岐も、平坦で読みやすい記述で実現できます。
複数の条件を組み合わせる論理パターン
C# 9.0から導入された論理パターンを用いると、and(かつ)、or(または)、not(〜でない)という直感的なキーワードで、複雑な条件を組み立てることができます。
比較演算と論理演算の融合
数域の判定や、複数のフラグが絡む条件分岐を、従来の比較演算子よりも宣言的に記述できます。

using System;
public class Program
{
public static void Main()
{
int temperature = 18;
string weatherMessage = temperature switch
{
< 0 => "凍えるような寒さです",
< 15 => "少し肌寒いですね",
< 25 => "過ごしやすい気温です",
100 => "沸騰しています!",
_ => "暑くなってきました"
};
Console.WriteLine(weatherMessage);
}
}
過ごしやすい気温です
論理パターンの演算子一覧
論理パターンで使用される主な演算子は以下の通りです。
| 演算子 | 説明 | 例 |
|---|---|---|
and | 両方のパターンに一致する場合に成功 | > 0 and < 10 |
or | いずれかのパターンに一致する場合に成功 | 1 or 2 or 3 |
not | パターンに一致しない場合に成功 | not null |
( ) | パターンの優先順位を明示する | (> 0 and < 10) or 100 |
特に not null は、従来の != null よりも意図が明確になるため、モダンなC#開発では積極的に活用されています。
位置パターンとタプルによる多角的な判定
位置パターン(Positional Pattern)は、オブジェクトを「分解(Deconstruct)」して、その要素の並び順に基づいてマッチングを行う手法です。
特に、複数の値をセットで扱う「タプル」との相性が抜群です。

using System;
public class Program
{
public static void Main()
{
var point = (10, 0); // タプル(x, y)
string positionInfo = point switch
{
(0, 0) => "原点にいます",
(var x, 0) => $"X軸上の {x} にいます",
(0, var y) => $"Y軸上の {y} にいます",
(var x, var y) => $"座標({x}, {y})にいます"
};
Console.WriteLine(positionInfo);
}
}
X軸上の 10 にいます
この手法は、自作のクラスでもDeconstructメソッドを実装していれば利用可能です。
複数の入力値を組み合わせて1つの結果を導き出したい場合に、「状態の組み合わせ」を網羅的に記述できるのが大きなメリットです。
リストパターンによる配列・リストの判定
C# 11で追加されたリストパターンは、配列やリストの内容、要素数、特定の順序を判定するための強力な機能です。
データの集まりを解析する際のコードが劇的に短縮されます。

using System;
public class Program
{
public static void Main()
{
int[] numbers = { 1, 2, 10, 20, 5 };
string analysis = numbers switch
{
[] => "空のリストです",
[1, 2, ..] => "1, 2 で始まるリストです", // .. は残りの要素(スプレッド)
[.., 5] => "5 で終わるリストです",
[var first, .., var last] => $"最初が {first}、最後が {last} です",
_ => "その他のリスト"
};
Console.WriteLine(analysis);
}
}
1, 2 で始まるリストです
[1, 2, ..] のように記述することで、「特定の要素で始まり、その後の長さは問わない」といった複雑な条件を、まるで正規表現のように簡潔に表現できます。
これは、CSVデータの解析や通信プロトコルのパケット解析などで非常に重宝します。
実践:switch式とパターンマッチングでリファクタリング
最後に、実際の業務でありそうな「複雑な条件分岐」を、パターンマッチングを使ってリファクタリングしてみましょう。
Before: 冗長なif文の羅列
以下は、会員ランクと購入金額に応じて割引率を計算する、従来の書き方の例です。
public double GetDiscount(User user, double price)
{
if (user == null) return 0;
if (user.Rank == Rank.Gold)
{
if (price >= 10000) return 0.2;
else return 0.1;
}
else if (user.Rank == Rank.Silver)
{
return 0.05;
}
return 0;
}
After: switch式による宣言的な記述
パターンマッチングを駆使すると、ビジネスルールがより明確に見えるようになります。
using System;
public enum Rank { Guest, Silver, Gold }
public record User(string Name, Rank Rank);
public class DiscountCalculator
{
public static double GetDiscount(User user, double price) => (user, price) switch
{
// ゴールド会員かつ1万円以上なら20%
( { Rank: Rank.Gold }, >= 10000 ) => 0.2,
// ゴールド会員なら一律10%
( { Rank: Rank.Gold }, _ ) => 0.1,
// シルバー会員なら5%
( { Rank: Rank.Silver }, _ ) => 0.05,
// それ以外(ゲストやnullを含む)は割引なし
_ => 0
};
}
public class Program
{
public static void Main()
{
var user = new User("田中", Rank.Gold);
double price = 15000;
double discount = DiscountCalculator.GetDiscount(user, price);
Console.WriteLine($"適用される割引率: {discount * 100}%");
}
}
適用される割引率: 20%
リファクタリング後のコードは、「どのような入力の組み合わせに対して、どのような結果を返すか」が表形式のように一目で把握できます。
条件の優先順位(上から評価される)も明確であり、保守性が極めて高いコードと言えるでしょう。
まとめ
C#のパターンマッチングとswitch式は、単なるシンタックスシュガー(構文上の糖衣)を超え、プログラムの設計思想そのものを変える力を持っています。
本記事の重要ポイント:
- switch式は値を返すため、変数代入やメソッド返却を簡潔にする。
- 型パターンにより、判定とキャストを同時に行い安全性を高める。
- プロパティパターンで、オブジェクトの内部状態を宣言的にチェックできる。
- 論理パターン(
and,or,not)で、複雑な数域や条件を読みやすく記述できる。 - リストパターンを活用すれば、配列データの構造解析が劇的に楽になる。
これらの機能を日常的に活用することで、「何をしているか」が伝わりやすい、意図の明確なコードを書くことができるようになります。
まずは既存のif-elseや古いswitch文を、一つずつモダンなパターンマッチングに置き換えるところから始めてみてください。
