パターンマッチング(C#)
『C#』のパターンマッチングは、値の型・構造・内容を同時に検査して変数に束縛できる機能です。is キーワードによる型パターン・定数パターン・プロパティパターン、switch 文と switch 式でのパターン記述、リストパターン(C# 11以降)、そして when 句によるガード条件を解説します。
構文
// 型パターン: 型の一致を判定し、一致したら変数へ代入します。
if (オブジェクト is 型 変数名) { ... }
// 定数パターン: 特定の値と一致するか判定します。
if (値 is 定数) { ... }
// プロパティパターン: プロパティの値が条件を満たすか判定します。
if (オブジェクト is { プロパティ名: 値 }) { ... }
// switch文でのパターン(C# 7以降): 型パターンとwhenガードを使えます。
switch (変数) {
case 型 v when 条件:
// 処理
break;
}
// switch式でのパターン(C# 8以降): 各アームをアロー(=>)で書きます。
var 結果 = 変数 switch {
型 v when 条件 => 返す値,
{ プロパティ名: 値 } => 返す値,
定数 => 返す値,
_ => デフォルト値
};
// リストパターン(C# 11以降): 配列やリストの要素をパターンで照合します。
if (配列 is [最初の要素, .., 最後の要素]) { ... }
パターンの種類一覧
| パターン名 | 書き方の例 | 概要 |
|---|---|---|
| 型パターン | obj is Worker s | オブジェクトが指定した型かどうかを調べ、一致したら変数 s に代入します。C# 7以降で使えます。 |
| 定数パターン | obj is null / obj is 42 | 値が特定の定数と等しいかどうかを調べます。 |
| プロパティパターン | s is { Level: "S" } | オブジェクトのプロパティが指定した値と一致するかどうかを調べます。C# 8以降で使えます。 |
| 関係パターン | n is >= 1000 | 数値などの大小比較をパターンで書きます。C# 9以降で使えます。 |
| 論理パターン | n is > 0 and < 100 | and / or / not でパターンを組み合わせます。C# 9以降で使えます。 |
| リストパターン | arr is [first, .., last] | 配列やリストの要素数・先頭・末尾などをパターンで照合します。C# 11以降で使えます。 |
| whenガード | case 型 v when 条件: | switch の case やアームに追加条件を付けます。C# 7以降で使えます。 |
サンプルコード
TypePattern.cs
using System;
class Worker {
public string Name { get; set; }
public string Level { get; set; }
public Worker(string name, string level) {
Name = name;
Level = level;
}
}
class Engineer : Worker {
public int Score { get; set; }
public Engineer(string name, string level, int score)
: base(name, level) {
Score = score;
}
}
// マネージャーのサブクラス: 評価値を持ちます。
class Manager : Worker {
public int Power { get; set; }
public Manager(string name, string level, int power)
: base(name, level) {
Power = power;
}
}
class TypePattern {
static void Main() {
// --- is による型パターン ---
// object 型の変数を is で型チェックし、一致した場合に変数へ代入します。
object entity = new Engineer("member_1", "S", 999999);
// is で型判定すると、同時に変数 s に代入されます。
if (entity is Engineer s) {
Console.WriteLine(s.Name + " のスコア: " + s.Score);
// → member_1 のスコア: 999999
}
// --- 定数パターン(null チェック)---
// null かどうかを is で判定できます。
object unknown = null;
if (unknown is null) {
Console.WriteLine("エンティティが null です。"); // こちらが実行されます。
}
// --- プロパティパターン ---
// { } 内にプロパティ名と期待する値を書きます。
var worker2 = new Engineer("member_2", "A", 80000);
if (worker2 is { Level: "A" }) {
Console.WriteLine(worker2.Name + " はレベルAのメンバーです。"); // こちらが実行されます。
}
// プロパティパターンと型パターンを組み合わせることもできます。
// レベルSかつスコアが 900000 以上の場合にのみ一致します。
if (entity is Engineer { Level: "S", Score: >= 900000 } topWorker) {
Console.WriteLine(topWorker.Name + " は最高評価のメンバーです。"); // こちらが実行されます。
}
// --- 関係パターンと論理パターン(C# 9以降)---
// 数値の大小をパターンで表現できます。
int rank = 85000;
if (rank is >= 80000 and < 1000000) {
Console.WriteLine("スコア " + rank + " は準Sクラスです。"); // こちらが実行されます。
}
}
}
コンパイルして実行すると次のようになります。
dotnet script TypePattern.cs member_1 のスコア: 999999 エンティティが null です。 member_2 はレベルAのメンバーです。 member_1 は最高評価のメンバーです。 スコア 85000 は準Sクラスです。
SwitchPattern.cs
using System;
class Member {
public string Name { get; set; }
public string Level { get; set; }
public Member(string name, string level) {
Name = name;
Level = level;
}
}
class Engineer : Member {
public int Score { get; set; }
public string SkillType { get; set; }
public Engineer(string name, string level, int score, string skill)
: base(name, level) {
Score = score;
SkillType = skill;
}
}
// タスク担当クラスです。
class TaskWorker : Member {
public int Strength { get; set; }
public TaskWorker(string name, string level, int strength)
: base(name, level) {
Strength = strength;
}
}
class SwitchPattern {
static void Main() {
// --- switch文でのパターンマッチング ---
// case に型パターンと when ガードを組み合わせて振り分けます。
object[] entities = {
new Engineer("member_1", "S", 999999, "skill_alpha"),
new Engineer("member_2", "A", 80000, "skill_beta"),
new TaskWorker("item_a", "S", 200000),
new TaskWorker("item_b", "S", 160000),
42
};
foreach (object entity in entities) {
switch (entity) {
// レベルSのエンジニア(スコア 900000 超え)の判定です。
case Engineer j when j.Level == "S" && j.Score > 900000:
Console.WriteLine("[最高] " + j.Name + "(" + j.SkillType + ")");
break;
case Engineer j:
Console.WriteLine("[エンジニア] " + j.Name + " / " + j.Level + " / スコア: " + j.Score);
break;
// レベルSのタスク担当の判定です。
case TaskWorker c when c.Level == "S":
Console.WriteLine("[Sタスク] " + c.Name + " / 強さ: " + c.Strength);
break;
// それ以外の型です。
default:
Console.WriteLine("[不明] " + entity);
break;
}
}
Console.WriteLine();
// --- switch式でのパターンマッチング ---
// 値を返すだけのケースはswitch式でコンパクトに書けます。
foreach (object entity in entities) {
string label = entity switch {
// プロパティパターンと when ガードを組み合わせます。
Engineer { Level: "S" } j when j.Score > 900000
=> "【最高格】" + j.Name,
Engineer j
=> "【エンジニア】" + j.Name + "(" + j.Level + ")",
TaskWorker { Level: "S" } c
=> "【Sタスク】" + c.Name,
TaskWorker c
=> "【タスク】" + c.Name + "(" + c.Level + ")",
// null パターンです。
null
=> "(null)",
// ワイルドカード: 上記いずれにも一致しない場合のデフォルトです。
_
=> "【その他】" + entity
};
Console.WriteLine(label);
}
}
}
コンパイルして実行すると次のようになります。
dotnet script SwitchPattern.cs [最高] member_1(skill_alpha) [エンジニア] member_2 / A / スコア: 80000 [Sタスク] item_a / 強さ: 200000 [Sタスク] item_b / 強さ: 160000 [不明] 42 【最高格】member_1 【エンジニア】member_2(A) 【Sタスク】item_a 【Sタスク】item_b 【その他】42
ListPattern.cs
using System;
class ListPattern {
static void Main() {
// --- リストパターン(C# 11以降)---
// 配列やリストの要素数・先頭・末尾などをパターンで照合できます。
// 「..」(スライスパターン)は0個以上の任意の要素に一致します。
string[][] teams = {
new[] { "member_2" },
new[] { "member_2", "member_3" },
new[] { "member_2", "member_3", "member_4" },
new[] { "member_1", "member_5", "member_6", "member_7" },
new string[0] // 空の配列
};
foreach (string[] team in teams) {
string result = team switch {
// 空の配列に一致します。
[] =>
"チーム人数が0人です。",
// 1人だけのチームに一致します。
[var only] =>
"単独作業: " + only,
// 先頭が "member_2" の2人チームに一致します。
["member_2", var partner] =>
"member_2チーム(2人): member_2 & " + partner,
// 先頭が "member_2" で3人以上のチームに一致します。
["member_2", ..] =>
"member_2チーム(3人以上): " + string.Join(" / ", team),
// 先頭の要素を first、末尾の要素を last として取り出します。
[var first, .., var last] =>
"リーダー: " + first + " / 末席: " + last + "(計" + team.Length + "人)",
// 念のためのデフォルトです(すべてのケースが網羅されているため実際は不要)。
_ =>
"照合不能"
};
Console.WriteLine(result);
}
Console.WriteLine();
// --- リストパターンで数値配列の構造を確認します ---
// スコアの履歴(int 配列)を照合します。
int[][] scoreLogs = {
new[] { 80000, 85000, 90000 }, // 上昇傾向
new[] { 100000, 95000, 88000 }, // 下降傾向
new[] { 75000 }, // 記録1件
new int[0] // 記録なし
};
foreach (int[] log in scoreLogs) {
string trend = log switch {
// 空の記録です。
[] =>
"記録なし",
// 記録が1件だけです。
[var only] =>
"記録: " + only,
// 先頭より末尾が大きければ上昇傾向です。
[var first, .., var last] when last > first =>
"上昇傾向: " + first + " → " + last,
// 先頭より末尾が小さければ下降傾向です。
[var first, .., var last] when last < first =>
"下降傾向: " + first + " → " + last,
// 先頭と末尾が等しければ横ばいです。
_ =>
"横ばい"
};
Console.WriteLine(trend);
}
}
}
コンパイルして実行すると次のようになります。
dotnet script ListPattern.cs 単独作業: member_2 member_2チーム(2人): member_2 & member_3 member_2チーム(3人以上): member_2 / member_3 / member_4 リーダー: member_1 / 末席: member_7(計4人) チーム人数が0人です。 上昇傾向: 80000 → 90000 下降傾向: 100000 → 88000 記録: 75000 記録なし
よくあるミス
型パターンで代入された変数を if ブロック外で使う
if (entity is Engineer s) の変数 s は、型が一致した場合にのみ代入されます。if ブロックの外では s に値が入っていない可能性があるため、コンパイラが警告またはエラーを出します。型パターンで代入された変数は必ず if ブロック内で使用してください。
object entity = "文字列データ";
if (entity is int n) {
Console.WriteLine(n * 2);
}
// Console.WriteLine(n); // エラー: n は確実に代入されていません。
switch 式でワイルドカード(_)を忘れる
switch 式ではすべてのケースを網羅する必要があります。ワイルドカードパターン _ がないと、どのアームにも一致しない値が渡された場合に実行時例外(SwitchExpressionException)が発生します。
object value = 3.14;
// string result = value switch {
// int n => "整数",
// string s => "文字列",
// };
string result = value switch {
int n => "整数",
string s => "文字列",
_ => "その他"
};
概要
『C#』のパターンマッチングは、複数の条件(型・プロパティの値・数値の範囲など)をまとめて一度に調べるための機能です。型キャストとnullチェックを別々に書く必要がなくなるため、コードをシンプルに保てます。
型パターン(is 型 変数名)は最も基本的なパターンです。if (entity is Engineer s) のように書くと、型チェックと変数への代入が一行で完了します。型が一致しなかった場合、変数 s は代入されないため if ブロック外では使わないようにしてください。
プロパティパターン({ プロパティ名: 値 })は、オブジェクトの内部状態を直接パターンに書けます。型パターンと組み合わせて is Engineer { Level: "S" } s のように書くと、型チェックとプロパティ検査を同時に行えます。
リストパターン(C# 11以降)は配列やリストの要素構造をパターンで照合できます。スライスパターン(..)を使うと「任意の個数の要素」を読み飛ばしつつ先頭・末尾だけを取り出す処理が簡潔に書けます。
when ガードはパターンに追加の条件を付けるためのものです。型が一致していても when の条件を満たさなければ次のアーム(ケース)へ進みます。複雑な条件を複数のアームに分散させることで、if / else if の長い連鎖を整理できます。
switch 文と switch 式の使い分けや基本的な書き方は『switch文 / switch式』を参照してください。型の安全なキャストは『is / as / パターンマッチ』も合わせて参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。