C#パターンマッチング完全ガイド|switch・isの使い方から実務で役立つ書き方まで解説

はじめに

C#で条件分岐を書くとき、if文や従来のswitch文だけで処理を組み立てていると、型チェック、nullチェック、値の比較、オブジェクトの状態判定が増えるにつれてコードが読みにくくなることがあります。

そこで役立つのがC#のパターンマッチングです。パターンマッチングを使うと、isswitchを使って「値がどのような形・型・状態か」を判定しながら、必要に応じて変数へ取り出すことができます。MicrosoftのC#ドキュメントでも、is式、switch文、switch式を使って、入力式を複数の特徴に照合できる機能として説明されています。

この記事では、c# パターンマッチングを学びたい初心者から、実務でより読みやすい分岐処理を書きたい開発者までを対象に、isswitchswitch式when句、プロパティパターン、リストパターンなどを体系的に解説します。

1. C#のパターンマッチングとは

C#のパターンマッチングとは、ある値が「特定の型である」「特定の値である」「特定の範囲にある」「特定のプロパティ状態を持つ」といった条件に一致するかを、パターンとして判定する仕組みです。

たとえば、次のようにobject型の値がstringかどうかを確認し、同時にtextという変数として扱えます。

C#
object value = "Hello";

if (value is string text)
{
Console.WriteLine(text.Length);
}

従来であれば、型チェックとキャストを別々に書く必要がありました。

C#
if (value is string)
{
string text = (string)value;
Console.WriteLine(text.Length);
}

パターンマッチングを使うと、判定と変数宣言をまとめられるため、コードが短く、意図も明確になります。

1-1. パターンマッチングでできること

C#のパターンマッチングでは、主に次のような判定ができます。

C#
object value = 123;

string result = value switch
{
int number => $"整数: {number}",
string text => $"文字列: {text}",
null => "nullです",
_ => "その他の型です"
};

この例では、valueの実行時の型や値に応じて処理を分けています。C#では、宣言パターン、型パターン、定数パターン、関係パターン、プロパティパターン、リストパターン、varパターン、discardパターンなど、複数のパターンがサポートされています。

パターンマッチングでできることを整理すると、次のようになります。

判定内容使用例
型を判定するvalue is string text
値を判定するstatus is 200
nullを判定するvalue is null
null以外を判定するvalue is not null
数値範囲を判定するscore is >= 80
複数条件を組み合わせるscore is >= 0 and <= 100
プロパティの状態を判定するuser is { IsActive: true }
配列やリストの形を判定するitems is [1, 2, _]

1-2. if文・従来のswitch文との違い

if文は、真偽値を返す条件式を使って分岐します。

C#
if (score >= 80)
{
grade = "A";
}
else if (score >= 60)
{
grade = "B";
}
else
{
grade = "C";
}

一方、パターンマッチングを使ったswitch式では、値の条件を並べる形で書けます。

C#
string grade = score switch
{
>= 80 => "A",
>= 60 => "B",
_ => "C"
};

従来のswitch文は、主に定数値ごとの分岐に使われることが多い構文でした。しかし現在のC#では、switch文もパターンマッチングに対応しており、型、範囲、プロパティなどを条件として扱えます。Microsoftのドキュメントでも、switch文は式とのパターン一致に基づいて実行するステートメントを選択すると説明されています。

1-3. パターンマッチングを使うメリット

C#でパターンマッチングを使う主なメリットは、次の3つです。

1つ目は、型チェックと変数宣言を同時に書けることです。

C#
if (input is DateTime date)
{
Console.WriteLine(date.ToString("yyyy-MM-dd"));
}

2つ目は、条件分岐を簡潔に整理できることです。

C#
string message = statusCode switch
{
200 => "成功",
400 => "不正なリクエスト",
404 => "見つかりません",
500 => "サーバーエラー",
_ => "その他のエラー"
};

3つ目は、オブジェクトの状態判定を読みやすく書けることです。

C#
if (user is { IsActive: true, Role: "Admin" })
{
Console.WriteLine("有効な管理者です");
}

特に実務では、APIレスポンス、DTO、ドメインオブジェクト、バリデーション結果などの状態判定で役立ちます。複雑なif文を減らし、「どの条件で何を返すのか」を一覧として表現できる点が大きな強みです。

1-4. C#のバージョンごとの主な対応機能

C#のパターンマッチングは、バージョンアップとともに機能が拡張されてきました。MicrosoftのC#バージョン履歴では、C# 7.0でパターンマッチングが導入され、C# 8.0でswitch式やプロパティパターンなどが追加され、C# 9.0で関係パターンや論理パターンなどが強化されたことが説明されています。

C#バージョン主なパターンマッチング関連機能
C# 7.0isによる宣言パターン、switch文での型パターンなど
C# 8.0switch式、プロパティパターン、位置指定パターンなど
C# 9.0関係パターン、論理パターン、notandorなど
C# 10.0拡張プロパティパターンなど
C# 11.0リストパターンなど
C# 12以降既存構文と組み合わせた表現力の向上が中心

注意点として、使用できる構文はプロジェクトのC#言語バージョンに依存します。新しい構文を使ってコンパイルエラーになる場合は、.csprojLangVersionやターゲットフレームワークを確認しましょう。Microsoftのコンパイラメッセージのドキュメントでも、設定された言語バージョンが機能をサポートしていない場合にエラーが発生することが説明されています。

2. C#パターンマッチングの基本構文

C#パターンマッチングの基本は、isswitchです。

isは「この値は指定したパターンに一致するか」を判定します。switchは「複数のパターンのうち、どれに一致するか」によって処理を分岐します。

2-1. is式を使った基本的な型判定

もっとも基本的な使い方は、isによる型判定です。

C#
object value = "C#";

if (value is string)
{
Console.WriteLine("文字列です");
}

このコードでは、valuestring型として扱える場合にtrueになります。is演算子は、式の実行時の型が指定した型と互換性があるかを確認するために使われます。

ただし、この書き方ではstring型であることは判定できますが、その値をstring変数として使うには別途キャストが必要です。

C#
if (value is string)
{
string text = (string)value;
Console.WriteLine(text.Length);
}

このような場面では、次に紹介する宣言パターンを使うと便利です。

2-2. isで型チェックと変数宣言を同時に行う方法

isでは、型チェックと同時に変数宣言ができます。

C#
object value = "Pattern Matching";

if (value is string text)
{
Console.WriteLine(text.ToUpper());
}

value is string textは、「valuestringとして扱えるなら、textという変数に代入する」という意味です。これにより、キャスト処理を明示的に書かずに済みます。

数値型でも同じように使えます。

C#
object value = 100;

if (value is int number)
{
Console.WriteLine(number * 2);
}

この書き方は、object型、外部APIから受け取った値、イベント引数、基底クラス型、インターフェース型などを扱う場面でよく使います。

2-3. switch文でパターンマッチングを使う方法

switch文でもパターンマッチングを使えます。

C#
static void PrintValue(object value)
{
switch (value)
{
case int number:
Console.WriteLine($"整数: {number}");
break;

case string text:
Console.WriteLine($"文字列: {text}");
break;

case null:
Console.WriteLine("nullです");
break;

default:
Console.WriteLine("その他です");
break;
}
}

この例では、object型のvalueを型ごとに分岐しています。case int number:のように書くことで、型チェックと変数宣言を同時に行えます。

switch文は、分岐ごとに複数行の処理を行いたい場合に向いています。

C#
switch (command)
{
case "start":
Start();
Log("開始しました");
break;

case "stop":
Stop();
Log("停止しました");
break;

default:
Log("不明なコマンドです");
break;
}

2-4. switch式で条件分岐を簡潔に書く方法

switch式は、条件に応じて値を返したいときに便利です。

C#
string label = score switch
{
>= 90 => "優秀",
>= 70 => "合格",
>= 0 => "再試験",
_ => "不正な点数"
};

switch式では、casebreakを書かずに、パターン => 結果という形で分岐を書きます。Microsoftのドキュメントでも、switch式は入力式が一致するパターンに基づいて値を計算できる構文として説明されています。

switch式は、特に「条件に応じて戻り値を決める」場面でコードを短くできます。

C#
static string GetStatusMessage(int statusCode) => statusCode switch
{
200 => "OK",
400 => "Bad Request",
404 => "Not Found",
500 => "Internal Server Error",
_ => "Unknown"
};

2-5. when句で追加条件を指定する方法

when句を使うと、パターンに追加条件を付けられます。

C#
static string GetMessage(object value)
{
return value switch
{
int number when number > 0 => "正の整数です",
int number when number < 0 => "負の整数です",
int => "0です",
string text when text.Length > 10 => "長い文字列です",
string => "文字列です",
null => "nullです",
_ => "その他です"
};
}

when句は、型や値のパターンだけでは表現しにくい条件を追加したいときに便利です。

ただし、when句に複雑な条件を書きすぎると、かえって可読性が下がります。単純な範囲判定であれば、関係パターンや論理パターンを使ったほうが読みやすい場合があります。

C#
// when句を使う例
score switch
{
int s when s >= 0 && s <= 100 => "有効",
_ => "無効"
};

// 関係パターンと論理パターンを使う例
score switch
{
>= 0 and <= 100 => "有効",
_ => "無効"
};

3. isを使ったパターンマッチングの書き方

isを使ったパターンマッチングは、C#初心者が最初に覚えるべき重要な構文です。特に、型チェック、nullチェック、値の判定を簡潔に書ける点が魅力です。

3-1. 型パターンの使い方

型パターンは、値が指定した型に一致するかを判定します。

C#
static void PrintLength(object value)
{
if (value is string text)
{
Console.WriteLine(text.Length);
}
}

value is string textと書くことで、valuestring型ならtextとして使えます。

インターフェースに対しても使えます。

C#
if (service is IDisposable disposable)
{
disposable.Dispose();
}

このように、具象クラスだけでなく、インターフェースや基底クラスを対象にできる点も実務で便利です。

3-2. 定数パターンの使い方

定数パターンは、値が特定の定数に一致するかを判定します。

C#
int statusCode = 404;

if (statusCode is 404)
{
Console.WriteLine("ページが見つかりません");
}

文字列にも使えます。

C#
string role = "Admin";

if (role is "Admin")
{
Console.WriteLine("管理者です");
}

ただし、単純な等価比較だけなら、次のように==を使ったほうが自然な場合もあります。

C#
if (role == "Admin")
{
Console.WriteLine("管理者です");
}

定数パターンは、switch式の中で使うと特に読みやすくなります。

C#
string message = statusCode switch
{
200 => "成功",
400 => "リクエストエラー",
404 => "未検出",
500 => "サーバーエラー",
_ => "不明"
};

3-3. nullチェックを簡潔に書く方法

C#パターンマッチングでは、nullチェックも簡潔に書けます。

C#
if (value is null)
{
Console.WriteLine("nullです");
}

nullではないことを確認する場合は、is not nullを使います。

C#
if (value is not null)
{
Console.WriteLine(value.ToString());
}

従来の書き方と比較すると、次のようになります。

C#
// 従来の書き方
if (value != null)
{
Console.WriteLine(value.ToString());
}

// パターンマッチングを使う書き方
if (value is not null)
{
Console.WriteLine(value.ToString());
}

is nullis not nullは、演算子オーバーロードの影響を避けてnullを判定したい場面でも使われます。nullable参照型を有効にしているプロジェクトでは、nullの可能性を明示する意味でも読みやすい書き方です。

3-4. varパターンの使い方

varパターンは、任意の値を変数に受け取るパターンです。

C#
if (value is var x)
{
Console.WriteLine(x);
}

この例では、基本的にどのような値でもxに入ります。ただし、単独で使うと「わざわざis varを使う意味」が薄くなることもあります。

varパターンは、switch式やプロパティパターンと組み合わせると役立ちます。

C#
string result = value switch
{
string text => $"文字列: {text}",
int number => $"整数: {number}",
var other => $"その他: {other}"
};

この例では、どのパターンにも一致しなかった値をotherとして受け取っています。

3-5. discardパターン「_」の使い方

discardパターンの_は、「何でも一致するが、値は使わない」という意味です。

C#
string message = statusCode switch
{
200 => "成功",
404 => "見つかりません",
_ => "その他"
};

_は、switch式のデフォルトケースとしてよく使われます。

C#
static string GetTypeName(object value) => value switch
{
int => "int",
string => "string",
bool => "bool",
null => "null",
_ => "unknown"
};

注意点として、_は何にでも一致するため、先頭に置くと後続のパターンが実行されません。

C#
// 悪い例
string result = value switch
{
_ => "すべてここに入る",
string => "文字列"
};

このようなコードでは、stringの分岐に到達できないため、コンパイラ警告やエラーの原因になります。

4. switchを使ったパターンマッチングの書き方

switchを使ったパターンマッチングには、switch文switch式があります。どちらも条件分岐に使えますが、目的が少し異なります。

4-1. switch文とswitch式の違い

switch文は、条件に応じて処理を実行するための構文です。

C#
switch (status)
{
case OrderStatus.Pending:
NotifyPending();
break;

case OrderStatus.Shipped:
NotifyShipped();
break;

default:
NotifyUnknown();
break;
}

一方、switch式は、条件に応じて値を返すための構文です。

C#
string label = status switch
{
OrderStatus.Pending => "処理待ち",
OrderStatus.Shipped => "発送済み",
OrderStatus.Canceled => "キャンセル",
_ => "不明"
};

使い分けの目安は次の通りです。

構文向いているケース
switch文複数行の処理、メソッド呼び出し、副作用のある処理
switch式値を返す処理、代入、return、式として使いたい処理

単純に値を返すだけなら、switch式のほうが短く書けます。

4-2. switch式の基本構文

switch式の基本構文は次の通りです。

C#
var result = target switch
{
pattern1 => value1,
pattern2 => value2,
_ => defaultValue
};

具体例を見てみましょう。

C#
static string GetDayType(DayOfWeek day) => day switch
{
DayOfWeek.Saturday => "休日",
DayOfWeek.Sunday => "休日",
_ => "平日"
};

switch式では、最初に一致したアームの式が結果として返されます。Microsoftのドキュメントでも、switch式は候補となる式の一覧から、入力式に一致するパターンに基づいて評価されると説明されています。

4-3. 複数条件をswitch式で整理する方法

複数条件を整理したい場合、switch式は非常に便利です。

C#
static string GetDiscountRank(int age, bool isMember)
{
return (age, isMember) switch
{
(>= 65, true) => "シニア会員割引",
(>= 65, false) => "シニア割引",
(< 18, true) => "学生会員割引",
(< 18, false) => "学生割引",
(_, true) => "会員割引",
_ => "通常料金"
};
}

この例では、タプルを使ってageisMemberの組み合わせを判定しています。if文で書くとネストが深くなりやすい条件も、switch式なら一覧として整理できます。

C#
static string GetShippingFee(decimal amount, bool isPremium)
{
return (amount, isPremium) switch
{
(>= 10000m, _) => "送料無料",
(>= 5000m, true) => "送料無料",
(_, true) => "送料半額",
_ => "通常送料"
};
}

4-4. デフォルトケースを「_」で書く方法

switch式では、デフォルトケースとして_を使うのが一般的です。

C#
string message = errorCode switch
{
"E001" => "入力エラー",
"E002" => "認証エラー",
"E003" => "権限エラー",
_ => "不明なエラー"
};

_を書いておくことで、想定外の値が来た場合にも処理を定義できます。

ただし、何でも_で握りつぶせばよいわけではありません。想定外の値を明確にエラーにしたい場合は、例外を投げることもあります。

C#
string label = status switch
{
OrderStatus.Pending => "処理待ち",
OrderStatus.Shipped => "発送済み",
OrderStatus.Canceled => "キャンセル",
_ => throw new ArgumentOutOfRangeException(nameof(status))
};

実務では、「未知の値でも画面表示を継続したい」のか、「未知の値は不具合として検知したい」のかによって、_の扱いを決めることが重要です。

4-5. switch式で戻り値をシンプルに返す方法

switch式は、メソッドの戻り値を直接返すときに特に便利です。

C#
static string GetPriorityLabel(Priority priority) => priority switch
{
Priority.Low => "低",
Priority.Normal => "通常",
Priority.High => "高",
Priority.Critical => "緊急",
_ => "不明"
};

return文と組み合わせても使えます。

C#
static decimal CalculateFee(User user)
{
return user switch
{
{ IsPremium: true } => 0m,
{ Age: < 18 } => 500m,
{ Age: >= 65 } => 700m,
_ => 1000m
};
}

このように、switch式を使うと「条件」と「返す値」の対応関係が見やすくなります。

5. C#で使える主なパターンの種類

C#のパターンマッチングには、さまざまなパターンがあります。ここでは、実務でよく使うものを中心に解説します。

5-1. 型パターン

型パターンは、値が指定した型に一致するかを判定します。

C#
static string Describe(object value) => value switch
{
int => "整数",
string => "文字列",
bool => "真偽値",
null => "null",
_ => "その他"
};

変数として使わない場合は、intstringのように型だけを書けます。

C#
if (value is string)
{
Console.WriteLine("文字列です");
}

型だけを判定したい場合に有効です。

5-2. 宣言パターン

宣言パターンは、型チェックと変数宣言を同時に行います。

C#
if (value is string text)
{
Console.WriteLine(text.Length);
}

switchでも使えます。

C#
string result = value switch
{
int number => $"整数: {number}",
string text => $"文字列: {text}",
DateTime date => $"日付: {date:yyyy-MM-dd}",
_ => "その他"
};

実務では、object型や基底クラス型を扱うときに多用します。

5-3. 定数パターン

定数パターンは、特定の値と一致するかを判定します。

C#
string result = statusCode switch
{
200 => "OK",
201 => "Created",
400 => "Bad Request",
404 => "Not Found",
500 => "Internal Server Error",
_ => "Unknown"
};

nullも定数パターンとして扱えます。

C#
if (value is null)
{
Console.WriteLine("nullです");
}

5-4. 関係パターン

関係パターンは、>, >=, <, <=を使って値の範囲を判定するパターンです。C# 9.0で追加された代表的なパターンマッチング機能のひとつです。

C#
static string GetGrade(int score) => score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};

範囲チェックにも使えます。

C#
if (age is >= 0 and <= 120)
{
Console.WriteLine("有効な年齢です");
}

5-5. 論理パターン

論理パターンは、andornotを使って複数のパターンを組み合わせる機能です。

C#
static string GetAgeGroup(int age) => age switch
{
< 0 => "不正",
>= 0 and <= 12 => "子ども",
>= 13 and <= 19 => "未成年",
>= 20 and < 65 => "成人",
>= 65 => "高齢者"
};

orを使う例です。

C#
static bool IsWeekend(DayOfWeek day) =>
day is DayOfWeek.Saturday or DayOfWeek.Sunday;

notを使う例です。

C#
if (value is not null)
{
Console.WriteLine("値があります");
}

andornotには優先順位があります。Microsoftのドキュメントでは、パターン結合子のバインド順序はnotandorの順であると説明されています。複雑な条件では、意図を明確にするためにかっこを使うと安全です。

C#
bool isValid = c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

5-6. プロパティパターン

プロパティパターンは、オブジェクトのプロパティの値を条件にして判定するパターンです。

C#
if (user is { IsActive: true })
{
Console.WriteLine("有効なユーザーです");
}

複数のプロパティも同時に判定できます。

C#
if (user is { IsActive: true, Role: "Admin" })
{
Console.WriteLine("有効な管理者です");
}

ネストしたプロパティも判定できます。

C#
if (order is { Customer: { IsVip: true }, TotalAmount: >= 10000m })
{
Console.WriteLine("VIP顧客の高額注文です");
}

プロパティパターンは、DTOやレスポンスオブジェクトの状態判定に非常に向いています。

C#
string result = response switch
{
{ IsSuccess: true, Data: not null } => "成功",
{ IsSuccess: false, ErrorCode: "NOT_FOUND" } => "見つかりません",
{ IsSuccess: false } => "失敗",
_ => "不明"
};

5-7. 位置指定パターン

位置指定パターンは、オブジェクトを分解して、それぞれの位置の値に対してパターンを適用する機能です。主にタプルやDeconstructメソッドを持つ型で使います。

C#
var point = (X: 10, Y: 20);

string area = point switch
{
(0, 0) => "原点",
(> 0, > 0) => "第1象限",
(< 0, > 0) => "第2象限",
(< 0, < 0) => "第3象限",
(> 0, < 0) => "第4象限",
_ => "軸上"
};

独自クラスでもDeconstructを定義すれば使えます。

C#
public class Point
{
public int X { get; }
public int Y { get; }

public Point(int x, int y)
{
X = x;
Y = y;
}

public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}

var point = new Point(1, 2);

string result = point switch
{
(0, 0) => "原点",
(> 0, > 0) => "第1象限",
_ => "その他"
};

5-8. リストパターン

リストパターンは、配列やリストの要素の並びを判定するパターンです。C# 11で導入された機能で、リストや配列の形に対してパターンマッチングを行えます。

C#
int[] numbers = { 1, 2, 3 };

string result = numbers switch
{
[1, 2, 3] => "1, 2, 3です",
[1, _, _] => "1から始まる3要素です",
[] => "空です",
_ => "その他です"
};

..を使うと、残りの要素を表せます。

C#
string result = numbers switch
{
[1, ..] => "1から始まります",
[.., 9] => "9で終わります",
[_, _, _] => "3要素です",
_ => "その他です"
};

コマンドライン引数のような配列を判定する場合にも便利です。

C#
static string ParseCommand(string[] args) => args switch
{
["create", var name] => $"作成: {name}",
["delete", var id] => $"削除: {id}",
["list"] => "一覧表示",
_ => "不明なコマンド"
};

6. 実務で役立つC#パターンマッチングの活用例

ここからは、実務でよくあるケースをもとに、C#パターンマッチングの使い方を紹介します。

6-1. object型の値を型ごとに処理する

外部ライブラリや汎用的な処理では、object型を扱うことがあります。パターンマッチングを使うと、型ごとの処理を安全に書けます。

C#
static string FormatValue(object? value)
{
return value switch
{
null => "(null)",
int number => number.ToString("N0"),
decimal amount => amount.ToString("C"),
DateTime date => date.ToString("yyyy-MM-dd"),
string text => text,
bool flag => flag ? "はい" : "いいえ",
_ => value.ToString() ?? string.Empty
};
}

if文で書くよりも、型と処理の対応関係が一覧化されるため、後から型を追加しやすくなります。

6-2. enumの分岐処理を読みやすくする

enumの表示名や処理を分岐する場合、switch式がよく使われます。

C#
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Canceled
}

static string GetLabel(OrderStatus status) => status switch
{
OrderStatus.Pending => "支払い待ち",
OrderStatus.Paid => "支払い済み",
OrderStatus.Shipped => "発送済み",
OrderStatus.Canceled => "キャンセル",
_ => "不明"
};

enumの値が増えた場合は、switch式の分岐も更新する必要があります。_でデフォルトを用意するか、想定外の値では例外を投げるかは、要件に合わせて決めましょう。

C#
static string GetLabel(OrderStatus status) => status switch
{
OrderStatus.Pending => "支払い待ち",
OrderStatus.Paid => "支払い済み",
OrderStatus.Shipped => "発送済み",
OrderStatus.Canceled => "キャンセル",
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
};

6-3. nullチェックと型チェックを同時に行う

isの宣言パターンを使うと、nullチェックと型チェックを同時に行えます。

C#
object? value = GetValue();

if (value is string text)
{
Console.WriteLine(text.Trim());
}

このコードでは、valuenullの場合やstringではない場合、ifブロックには入りません。つまり、value != nullvalue is stringを別々に書く必要がありません。

空文字や空白も除外したい場合は、when句や追加条件を使います。

C#
if (value is string text && !string.IsNullOrWhiteSpace(text))
{
Console.WriteLine(text.Trim());
}

switch式で書くこともできます。

C#
string message = value switch
{
string text when !string.IsNullOrWhiteSpace(text) => text.Trim(),
string => "空の文字列です",
null => "nullです",
_ => "文字列ではありません"
};

6-4. 条件分岐のネストを減らす

パターンマッチングは、ネストしたif文を減らすのに役立ちます。

C#
// ネストが深い例
if (user != null)
{
if (user.IsActive)
{
if (user.Role == "Admin")
{
Console.WriteLine("有効な管理者です");
}
}
}

プロパティパターンを使うと、次のように書けます。

C#
if (user is { IsActive: true, Role: "Admin" })
{
Console.WriteLine("有効な管理者です");
}

さらに、判定結果を返す場合はswitch式が便利です。

C#
static string GetUserState(User? user) => user switch
{
null => "ユーザーが存在しません",
{ IsActive: false } => "無効なユーザーです",
{ Role: "Admin" } => "管理者です",
{ Role: "Member" } => "一般会員です",
_ => "その他のユーザーです"
};

条件が上から順番に評価されるため、「null」「無効」「管理者」「一般会員」のように優先順位を明確にできます。

6-5. DTOやレスポンスオブジェクトの状態判定に使う

APIレスポンスやDTOの状態判定にも、パターンマッチングは向いています。

C#
public class ApiResponse<T>
{
public bool IsSuccess { get; set; }
public T? Data { get; set; }
public string? ErrorCode { get; set; }
public string? Message { get; set; }
}

このようなレスポンスを判定する場合、プロパティパターンを使うと状態が読みやすくなります。

C#
static string GetResponseMessage(ApiResponse<User> response)
{
return response switch
{
{ IsSuccess: true, Data: not null } => "ユーザー取得に成功しました",
{ IsSuccess: true, Data: null } => "データがありません",
{ IsSuccess: false, ErrorCode: "NOT_FOUND" } => "ユーザーが見つかりません",
{ IsSuccess: false, ErrorCode: "UNAUTHORIZED" } => "認証が必要です",
{ IsSuccess: false, Message: var message } when !string.IsNullOrWhiteSpace(message) => message,
_ => "不明なエラーです"
};
}

この書き方なら、レスポンスの状態と返すメッセージの対応が明確です。

6-6. 例外処理やバリデーションで活用する

例外の型に応じて処理を変える場面でも、パターンマッチングは役立ちます。

C#
static string GetErrorMessage(Exception ex) => ex switch
{
ArgumentNullException => "必須項目が指定されていません",
ArgumentException => "引数が不正です",
InvalidOperationException => "現在の状態では操作できません",
TimeoutException => "タイムアウトしました",
_ => "予期しないエラーが発生しました"
};

バリデーションでも使えます。

C#
static string ValidateAge(int age) => age switch
{
< 0 => "年齢は0以上で入力してください",
> 120 => "年齢が大きすぎます",
_ => "OK"
};

複数項目の組み合わせもタプルで整理できます。

C#
static string ValidateUser(string? name, int age)
{
return (name, age) switch
{
(null or "", _) => "名前を入力してください",
(_, < 0) => "年齢は0以上で入力してください",
(_, > 120) => "年齢が大きすぎます",
_ => "OK"
};
}

7. パターンマッチングでコードを読みやすくするコツ

パターンマッチングは便利ですが、使い方を間違えると逆に読みにくくなることもあります。ここでは、実務で意識したい書き方のコツを紹介します。

7-1. if文よりswitch式が向いているケース

switch式が向いているのは、「1つの値や状態に応じて、結果を返す」ケースです。

C#
static string GetRank(int score) => score switch
{
>= 90 => "S",
>= 80 => "A",
>= 70 => "B",
>= 60 => "C",
_ => "D"
};

一方、条件ごとに複数の処理を行う場合や、副作用のある処理を細かく書く場合は、if文やswitch文のほうが自然です。

C#
if (user is { IsActive: true })
{
SendMail(user);
WriteLog(user);
UpdateLastNotifiedAt(user);
}

無理にすべてをswitch式にする必要はありません。値を返すならswitch式、処理を実行するならif文やswitch文、という使い分けを意識しましょう。

7-2. when句を使いすぎないための考え方

when句は便利ですが、複雑な条件を書きすぎると読みづらくなります。

C#
// 読みにくい例
string result = user switch
{
User u when u.IsActive && u.Role == "Admin" && u.LastLoginAt > DateTime.Now.AddDays(-30)
=> "最近ログインした管理者",
_ => "その他"
};

プロパティパターンで表現できる部分は、できるだけパターン側に寄せると読みやすくなります。

C#
string result = user switch
{
{ IsActive: true, Role: "Admin", LastLoginAt: var lastLogin }
when lastLogin > DateTime.Now.AddDays(-30)
=> "最近ログインした管理者",

_ => "その他"
};

さらに複雑な条件は、メソッドに切り出すのも有効です。

C#
string result = user switch
{
{ IsActive: true, Role: "Admin" } u when HasLoggedInRecently(u) => "最近ログインした管理者",
_ => "その他"
};

7-3. 複雑な条件を分割して保守性を高める

パターンマッチングで何でも1行に詰め込むと、かえって保守性が下がります。

C#
// 詰め込みすぎの例
return order switch
{
{ Customer: { IsVip: true, Rank: >= 3 }, TotalAmount: >= 10000m, Items.Count: > 5 } => "特別対応",
_ => "通常対応"
};

条件が業務ルールとして重要な場合は、意味のあるメソッドに分けると読みやすくなります。

C#
static bool IsSpecialOrder(Order order) =>
order is
{
Customer: { IsVip: true, Rank: >= 3 },
TotalAmount: >= 10000m
}
&& order.Items.Count > 5;

static string GetHandlingType(Order order) => IsSpecialOrder(order)
? "特別対応"
: "通常対応";

パターンマッチングは、条件を短くするためだけの機能ではありません。条件の意図を伝えるために使うことが大切です。

7-4. nullやデフォルトケースの漏れを防ぐ

switch式では、すべてのケースを網羅していないと警告が出ることがあります。Microsoftのドキュメントでは、パターンマッチングに関する警告として、網羅されていないswitch式や到達不能なパターンなどが説明されています。

たとえば、次のコードはPendingShippedしか扱っていません。

C#
string label = status switch
{
OrderStatus.Pending => "処理待ち",
OrderStatus.Shipped => "発送済み"
};

OrderStatusに他の値がある場合、未処理のケースが残ります。通常は_を追加します。

C#
string label = status switch
{
OrderStatus.Pending => "処理待ち",
OrderStatus.Shipped => "発送済み",
OrderStatus.Canceled => "キャンセル",
_ => "不明"
};

nullの可能性がある値を扱う場合も、明示的にnullを処理しましょう。

C#
string message = user switch
{
null => "ユーザーが存在しません",
{ IsActive: false } => "無効なユーザーです",
{ IsActive: true } => "有効なユーザーです"
};

7-5. 可読性が下がる書き方を避ける

パターンマッチングは強力ですが、複雑にしすぎると理解しづらくなります。

避けたい書き方の例です。

C#
return value switch
{
string s when s.Length > 0 && s.StartsWith("A") && s.EndsWith("Z") => "対象",
int n when n > 0 && n % 2 == 0 && n < 100 => "対象",
_ => "対象外"
};

このような場合は、判定ロジックを別メソッドに分けたほうがよいでしょう。

C#
return value switch
{
string s when IsTargetText(s) => "対象",
int n when IsTargetNumber(n) => "対象",
_ => "対象外"
};

読みやすいパターンマッチングのポイントは、「条件を短くすること」ではなく、「条件の意味がひと目で分かること」です。

8. C#パターンマッチングの注意点

C#パターンマッチングを実務で使うときは、評価順序、到達不能なパターン、網羅性、nullable参照型、言語バージョンに注意が必要です。

8-1. パターンの評価順序に注意する

switch式では、上から順番にパターンが評価され、最初に一致したものが使われます。

C#
static string GetScoreLabel(int score) => score switch
{
>= 0 => "0以上",
>= 80 => "高得点",
_ => "その他"
};

このコードでは、scoreが90でも最初の>= 0に一致するため、"高得点"にはなりません。正しくは、より具体的な条件を先に書きます。

C#
static string GetScoreLabel(int score) => score switch
{
>= 80 => "高得点",
>= 0 => "0以上",
_ => "その他"
};

パターンマッチングでは、条件の順序が結果に影響します。広い条件よりも、具体的な条件を先に書くのが基本です。

8-2. 到達不能なパターンが発生するケース

先に広いパターンを書くと、後続のパターンが到達不能になります。

C#
string result = value switch
{
object => "何らかのオブジェクト",
string => "文字列",
_ => "その他"
};

stringobjectでもあるため、stringの分岐には到達できません。正しくは、具体的な型を先に書きます。

C#
string result = value switch
{
string => "文字列",
object => "何らかのオブジェクト",
null => "null"
};

ただし、この例でもobjectはnull以外の多くの値に一致するため、nullを先に書いたほうが意図が明確です。

C#
string result = value switch
{
null => "null",
string => "文字列",
object => "何らかのオブジェクト"
};

8-3. 網羅性チェックとコンパイラ警告

switch式では、すべての入力値を処理できない可能性がある場合、コンパイラが警告を出すことがあります。パターンマッチングの警告には、網羅されていないswitch式や到達不能なパターンなどが含まれます。

C#
static string GetLabel(bool flag) => flag switch
{
true => "はい"
};

このコードでは、falseの場合がありません。修正するには、falseまたは_を追加します。

C#
static string GetLabel(bool flag) => flag switch
{
true => "はい",
false => "いいえ"
};

または、次のように書きます。

C#
static string GetLabel(bool flag) => flag switch
{
true => "はい",
_ => "いいえ"
};

enumの場合も同様です。すべての列挙値を明示するか、_でデフォルトケースを用意しましょう。

8-4. nullable参照型との関係

nullable参照型を有効にしている場合、nullの可能性をコンパイラが解析します。

C#
static int GetLength(string? text)
{
if (text is not null)
{
return text.Length;
}

return 0;
}

text is not nullの後では、textはnullではないものとして扱えるため、Lengthにアクセスできます。

宣言パターンもnullチェックを兼ねます。

C#
static int GetLength(object? value)
{
if (value is string text)
{
return text.Length;
}

return 0;
}

この場合、valueがnullであればstring textには一致しません。nullable参照型を使うプロジェクトでは、is not nullnull =>を明示的に書くことで、意図が伝わりやすくなります。

8-5. 古いC#バージョンや.NET Frameworkで使えない機能

パターンマッチングの機能は、C#の言語バージョンによって使える範囲が異なります。たとえば、switch式はC# 8.0以降、関係パターンや論理パターンはC# 9.0以降、リストパターンはC# 11.0以降の機能です。C#のバージョン履歴や機能仕様では、各バージョンで追加されたパターンマッチング機能が説明されています。

古いプロジェクトで次のようなコードを書くと、言語バージョンによってはコンパイルできません。

C#
string result = score switch
{
>= 80 => "合格",
_ => "不合格"
};

対処法としては、次の方法があります。

XML
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>

ただし、プロジェクトのターゲットフレームワークやチームの開発環境によっては、安易にlatestへ変更できない場合もあります。既存システムでは、利用可能なC#バージョンを確認したうえで導入しましょう。

9. C#パターンマッチングでよくあるエラーと対処法

ここでは、C#パターンマッチングを使うときによくあるエラーや警告と、その対処法を紹介します。

9-1. switch式で「すべてのケースを網羅していない」と警告される

switch式で一部のケースしか書いていない場合、未処理の値があるとして警告されることがあります。

C#
static string GetMessage(int code) => code switch
{
200 => "OK",
404 => "Not Found"
};

このコードでは、200404以外の値が来た場合に結果を返せません。_を追加しましょう。

C#
static string GetMessage(int code) => code switch
{
200 => "OK",
404 => "Not Found",
_ => "Unknown"
};

想定外の値をエラーとして扱いたい場合は、例外を投げます。

C#
static string GetMessage(int code) => code switch
{
200 => "OK",
404 => "Not Found",
_ => throw new ArgumentOutOfRangeException(nameof(code))
};

9-2. 型変換やキャストでエラーになる

パターンマッチングを使わずにキャストすると、実行時エラーになることがあります。

C#
object value = "123";

int number = (int)value; // 実行時エラー

安全に型を判定してから使うには、isを使います。

C#
if (value is int number)
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("intではありません");
}

文字列を数値に変換したい場合は、パターンマッチングではなくint.TryParseを使います。

C#
if (value is string text && int.TryParse(text, out int number))
{
Console.WriteLine(number);
}

パターンマッチングは「型として扱えるか」を判定する機能であり、「文字列を数値に変換する」機能ではありません。この違いを理解しておくことが重要です。

9-3. null判定が期待通りに動かない

isの宣言パターンは、nullには一致しません。

C#
object? value = null;

if (value is string text)
{
Console.WriteLine(text);
}
else
{
Console.WriteLine("stringではない、またはnullです");
}

nullを個別に扱いたい場合は、明示的にnullパターンを書きます。

C#
string result = value switch
{
null => "nullです",
string text => $"文字列: {text}",
_ => "その他です"
};

null以外を判定したい場合は、is not nullが読みやすいです。

C#
if (value is not null)
{
Console.WriteLine(value.ToString());
}

9-4. when句の条件が複雑になりすぎる

when句に条件を詰め込みすぎると、コードの意図が分かりにくくなります。

C#
string result = order switch
{
Order o when o.TotalAmount > 10000m && o.Customer != null && o.Customer.IsVip && o.Items.Count > 3
=> "特別注文",
_ => "通常注文"
};

プロパティパターンとメソッド分割を使うと、読みやすくできます。

C#
string result = order switch
{
{ TotalAmount: > 10000m, Customer: { IsVip: true } } o when HasManyItems(o)
=> "特別注文",
_ => "通常注文"
};

static bool HasManyItems(Order order) => order.Items.Count > 3;

when句は「パターンだけでは表現しにくい追加条件」に限定すると、保守しやすくなります。

9-5. _の使い方で意図しない分岐になる

_は何にでも一致するため、書く位置に注意が必要です。

C#
string result = value switch
{
_ => "その他",
int => "整数",
string => "文字列"
};

このコードでは、最初の_がすべてに一致するため、intstringには到達しません。正しくは、具体的な条件を先に書きます。

C#
string result = value switch
{
int => "整数",
string => "文字列",
_ => "その他"
};

_は最後に置くのが基本です。ただし、意図的に途中で広い条件を置く場合は、後続の条件が本当に必要かを確認しましょう。

10. C#パターンマッチングのよくある質問

10-1. パターンマッチングはいつ使うべきか

パターンマッチングは、型、値、範囲、オブジェクトの状態によって処理を分けたいときに使うべきです。

特に向いているケースは次の通りです。

ケース
型ごとに処理したいobjectintstringDateTimeで分岐
enumを表示名に変換したいOrderStatusからラベルを返す
範囲で判定したい点数、年齢、金額
DTOの状態を判定したいAPIレスポンス、バリデーション結果
nullチェックと型チェックをまとめたいvalue is string text

逆に、単純な真偽値の条件だけなら、if文のほうが自然な場合もあります。

C#
if (user.IsActive)
{
Console.WriteLine("有効です");
}

何でもパターンマッチングに置き換えるのではなく、条件の見通しがよくなる場面で使いましょう。

10-2. isとasの違いは何か

isは、値が指定した型やパターンに一致するかを判定します。

C#
if (value is string text)
{
Console.WriteLine(text.Length);
}

asは、指定した型に変換できる場合はその型の値を返し、変換できない場合はnullを返します。

C#
string? text = value as string;

if (text != null)
{
Console.WriteLine(text.Length);
}

現在のC#では、型チェックと変数宣言を同時に行えるisの宣言パターンが使いやすい場面が多くあります。Microsoftの型テストとキャストに関するドキュメントでも、isは実行時の型が指定した型と互換性があるかを確認し、asは互換性がある場合に指定型へ変換すると説明されています。

10-3. switch文とswitch式はどちらを使うべきか

値を返すだけなら、switch式がおすすめです。

C#
static string GetLabel(Status status) => status switch
{
Status.Active => "有効",
Status.Inactive => "無効",
_ => "不明"
};

複数行の処理を実行するなら、switch文が向いています。

C#
switch (status)
{
case Status.Active:
Activate();
WriteLog();
break;

case Status.Inactive:
Deactivate();
WriteLog();
break;
}

目安としては、「式として値を返したいならswitch式」「手続き的に処理を実行したいならswitch文」と考えると分かりやすいです。

10-4. パターンマッチングは処理速度に影響するか

通常の業務アプリケーションでは、パターンマッチングによる処理速度の差を過度に気にする必要はほとんどありません。多くの場合、可読性や保守性の向上のほうが重要です。

ただし、非常に高頻度で呼ばれる処理や、パフォーマンスが厳密に求められる処理では、ベンチマークを取って確認するのが確実です。

C#
// 可読性を優先して問題ないことが多い
string label = score switch
{
>= 80 => "合格",
_ => "不合格"
};

パターンマッチングは、コンパイラによって適切に処理されますが、複雑な条件や高コストなwhen句を大量に使えば、その条件評価の分だけコストは発生します。速度が気になる場合は、実測して判断しましょう。

10-5. C#初心者でも覚えるべき構文はどれか

C#初心者がまず覚えるべきパターンマッチングは、次の5つです。

構文用途
value is string text型チェックと変数宣言
value is nullnullチェック
value is not nullnull以外のチェック
switch値を返す条件分岐
_デフォルトケース

まずは、次のようなコードを書けるようになれば十分です。

C#
static string Describe(object? value) => value switch
{
null => "nullです",
string text => $"文字列: {text}",
int number => $"整数: {number}",
_ => "その他です"
};

慣れてきたら、関係パターン、論理パターン、プロパティパターンを学ぶと、実務で書けるコードの幅が広がります。

まとめ

C#のパターンマッチングは、型、値、範囲、オブジェクトの状態を分かりやすく判定するための強力な構文です。isを使えば型チェックと変数宣言を同時に行え、switch式を使えば条件に応じた戻り値を簡潔に表現できます。

基本的な使い方は、次の通りです。

C#
if (value is string text)
{
Console.WriteLine(text.Length);
}
C#
string result = value switch
{
null => "null",
string text => $"文字列: {text}",
int number => $"整数: {number}",
_ => "その他"
};

実務では、object型の処理、enumの分岐、APIレスポンスの状態判定、バリデーション、例外処理など、多くの場面で活用できます。

一方で、パターンの評価順序、到達不能なパターン、_の位置、when句の複雑化、C#言語バージョンには注意が必要です。広い条件より具体的な条件を先に書き、nullやデフォルトケースを明示し、複雑な条件はメソッドに分割すると、読みやすく保守しやすいコードになります。

c# パターンマッチングを使いこなす第一歩は、isによる型チェック、switch式による値の返却、_によるデフォルトケースを自然に書けるようになることです。そこから関係パターン、論理パターン、プロパティパターン、リストパターンへと学習を広げれば、C#の条件分岐をより安全で分かりやすく設計できるようになります。