C#のif・switch:パターンマッチング、CS8509、速度比較(.NET 10)
C#のif・elseとswitchを.NET 10で再検証。switch式、関係・論理・リストパターン、警告CS8509が実行時に意味すること、ifの連鎖とswitchの速度の実測まで、2021年版の誤りも訂正。
2026年11月改訂: 2021年に韓国語で書いた条件分岐の講座を、日本語向けに書き直しました。例を .NET 10.0.14(C# 14)でコンパイルし直したところ、
if・else if・入れ子のifの例は元の記事とまったく同じ出力でした。ただし元の説明のうち2点は今では誤りです。switch は整数・文字・文字列型しか使えないとしていましたが C# 7 以降は違いますし、偶奇を判定するswitch (a % 2)の例は負の数が来ると何も出力しません。この2点を直し、その後に加わった switch 式とパターンの文法、警告 CS8509 が実行時に意味すること、そして switch が if の連鎖より本当に速くなる場面の実測を加えました。
この記事は、分岐を学び始めた C# の初心者と、case … break; の形の switch しか知らないまま score switch { >= 90 => "A", _ => "F" } のようなコードに出会った開発者向けです。先に結論を書くと、条件ごとに調べる対象が違うなら if、1つの値を多数のケースと比べてケースごとに処理があるなら switch 文、ケースごとに値を返すだけなら switch 式です。速度が決め手になることはまれで、実測ではっきり速かったのは文字列キー30個の switch だけでした。
if・else if・else:2021年の例を .NET 10 で
if の条件式は必ず bool です。C と違って整数を bool に変換しないので、if (count) は error CS0029: Cannot implicitly convert type 'int' to 'bool' というコンパイルエラーになります。
int a = 50, b = 50;
if (a < b)
Console.WriteLine("b は a より大きい");
else if (a == b)
Console.WriteLine("a と b は等しい");
else
Console.WriteLine("a は b より大きい");
a と b は等しい
元の記事はこの例を3段階で直していき、その考え方は今も有効です。
ifを2つ別々に書く(a < bとa > b)と、1つ目が真でも2つ目の条件まで評価します。- 2つ目を
elseに替えると短くなりますが、a == bのときに間違えます。「a は b より大きい」と出てしまいます。a = 50, b = 50で実行すると、実際にその行が出ました。 else ifで等しい場合を別に扱えば解決します。
入れ子の例(if (a > 0) { …; if (a > 30) … })も2021年と同じく2行を出力しました。元の記事にない実務の注意点を1つ足すと、波かっこなしで if (x) DoA(); DoB(); と書くと、インデントに関係なく DoB() は常に実行されます。そのため波かっこを必須にしているチームが多くあります。
C# 9 からは if の条件にもパターンが使え、変数名を繰り返さずに済みます。
int t = 15;
if (t is >= 10 and < 20) Console.WriteLine("teen-ish");
string? maybe = null;
if (maybe is not null) Console.WriteLine(maybe.Length);
char c = 'k';
Console.WriteLine(c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') ? "letter" : "other");
teen-ish
letter
null チェックは is not null が定番です。!= null と違い、オーバーロードされた != 演算子の影響を受けません。
switch 文:2021年から変わったこと
元の曜日の例は今も動き、"수"(韓国語で「水」)なら水曜日、それ以外なら default の文を出力します。元の記事が説明したルールも変わっていません。break を外すと、.NET 10 はコード番号付きで2つのエラーを出します。
error CS0163: Control cannot fall through from one case label ('case 0:') to another
error CS8070: Control cannot fall out of switch from final case label ('case 1:')
元記事のエラーには個人フォルダーのパスが写っていたので省きました。C# は C のように暗黙に次の case へ流れ落ちません。その代わり、1つのブロックにラベルを重ねられ、本当に C のように続けて実行したいときは goto case を使います。
int n = 2;
switch (n)
{
case 1:
case 2:
Console.WriteLine("1 or 2");
goto case 3;
case 3:
Console.WriteLine("then 3");
break;
}
1 or 2
then 3
同じラベルを2回書くと error CS0152: The switch statement contains multiple cases with the label value '2' です。
もう当てはまらないのは「switch は整数・文字・文字列型しかサポートしない」という説明です。C# 7 から switch はどんな型でも受け取り、case はパターンになりました。C# 9 からは double に関係パターンも書けます。体温の判定ならこうです。
double temp = 36.9;
switch (temp)
{
case < 35.0: Console.WriteLine("low"); break;
case >= 37.5: Console.WriteLine("fever"); break;
default: Console.WriteLine("normal"); break;
}
normal
偶奇の例と負の数
2021年の記事は奇数と偶数をこう判定していました。
switch (a % 2) { case 0: Console.WriteLine("偶数"); break; case 1: Console.WriteLine("奇数"); break; }
a = 13 なら「奇数」と出ますが、a = -13 だと何も出力されません。C# では -13 % 2 が -1 なのでどの case にも当たらず、default もないからです。理由は C#の演算子 の記事で説明しています。a % 2 != 0 で調べるか、int.IsOddInteger(a) を使えば直ります。default のない switch はもう一度見直すべきだ、というよい例です。
switch 式とパターン
C# 8 で switch 式が加わりました。値を先に書き、各アームを パターン => 結果 で並べ、全体が1つの式なのでそのまま返したり代入したりできます。C# 9 では関係パターン(<、>=)と論理パターン(and、or、not)が入りました。
static string Grade(int score) => score switch
{
< 0 or > 100 => "invalid",
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
_ => "F",
};
foreach (var s in new[] { 95, 85, 72, 10, 101, -5 }) Console.Write($"{s}:{Grade(s)} ");
95:A 85:B 72:C 10:F 101:invalid -5:invalid
アームは上から順に調べられ、_ はすべての値に当たる破棄パターンです。コンパイラは順序もチェックします。>= 70 を >= 90 より前に書くと >= 90 のアームには決して当たらず、これは警告ではなくエラーです。
error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match.
型パターンとプロパティパターンを使うと、is のチェックとキャストを並べたコードを switch 1つにまとめられます。
static string Describe(object? o) => o switch
{
null => "null",
int n when n < 0 => $"negative int {n}",
int n => $"int {n}",
string { Length: 0 } => "empty string",
string s => $"string({s.Length})",
int[] { Length: > 3 } arr => $"long array, first {arr[0]}",
_ => o.GetType().Name,
};
null | negative int -3 | int 7 | empty string | string(3) | long array, first 1 | Double
タプルパターンは2つの値を一度に調べます。if の連鎖がいちばん早く読みにくくなるところです。じゃんけんの判定がよい例です。
static string Rps(string a, string b) => (a, b) switch
{
var (x, y) when x == y => "draw",
("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "win",
_ => "lose",
};
// Rps("rock","scissors") = win, Rps("paper","paper") = draw, Rps("rock","paper") = lose
リストパターン(C# 11)
C# 11 のリストパターンは、配列やリストを形で照合します。.. は0個以上の要素に当たり、var で値を取り出せます。コマンドライン引数の処理に向いています。
static string Cmd(string[] args) => args switch
{
[] => "no args",
["help"] => "help",
["add", var item] => $"add {item}",
["rm", .. var rest] when rest.Length > 0 => $"remove {rest.Length} item(s)",
[var first, .., var last] => $"{first}..{last}",
_ => "?",
};
[]→no args [help]→help [add,milk]→add milk [rm,a,b]→remove 2 item(s) [rm]→? [x,y,z]→x..z
[rm] に注目してください。rm のアームは要素0個で when が失敗し、[var first, .., var last] は要素が2個以上必要なので、_ に落ちます。リストパターンをテストするときは短い入力から確かめるのがおすすめです。
網羅性:CS8509 と実行時の挙動
switch 式はすべての入力に対して値を作らなければなりません。どのアームにも当たらない入力をコンパイラが見つけると警告します。
static string Sign(int n) => n switch { > 0 => "+", < 0 => "-" };
Console.WriteLine(Sign(0));
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered.
警告にすぎないのでビルドは通ります。そして実行すると Sign(0) で例外になります。
System.Runtime.CompilerServices.SwitchExpressionException: Non-exhaustive switch expression failed to match its input.
Unmatched value was 0.
列挙型(enum)にはもっと微妙なケースがあります。enum Color { Red, Green } で Red と Green を両方処理しても、別の警告が出ます。どんな整数でも enum にキャストできるからです。
warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Color)2' is not covered.
(Color)7 を渡すと SwitchExpressionException ... Unmatched value was 7. が発生しました。DB や JSON、あるいは別サービスの新しいバージョンから enum の値が来る場面では実際に起こります。
対処は次のとおりです。
- 意味のある例外を投げる(
throw new ArgumentOutOfRangeException(nameof(n)))か、安全な既定値を返す_ => …のアームを足します。 - コンパイラに強制させたいなら、プロジェクトに
<WarningsAsErrors>CS8509</WarningsAsErrors>を入れます。Signの例を-p:WarningsAsErrors=CS8509でビルドするとerror CS8509になりました。 - 従来の switch 文にはこのチェックがありません。偶奇の例のように、漏れたケースは黙って何もしません。
switch が if の連鎖より速くなる場面(実測)
よく「コンパイラがジャンプテーブルを作るので switch は速い」と言われます。ランダムな入力100万個を20回処理する(分岐2,000万回)ループをケースごとに [MethodImpl(NoInlining)] のメソッドにし、Release ビルド、Apple Silicon Mac(M1 Pro)でウォームアップ1回のあと3回計測しました。
| ケース | if の連鎖 | switch |
|---|---|---|
int の値10個(0〜9、それ以外は default) |
131 / 134 / 134ms | 139 / 141 / 137ms |
| 曜日の文字列7個(一致しない値を含む) | 224 / 226 / 222ms | 216 / 219 / 215ms |
文字列キー30個 key00〜key29 |
416 / 407 / 417ms | 296 / 292 / 286ms |
整数10個や文字列7個では、どちらに転んでも数%の差でした。同じマシンで先に走らせた回はもっとばらついていました(文字列の if が254〜284ms)。ランダムな入力では、どちらの書き方でも分岐予測ミスのコストが支配的です。はっきり速かったのは文字列キー30個の switch だけで、約30%速くなりました。文字列の case が多いと、C# コンパイラは入力を1つずつ順に比べる代わりに、長さや特定位置の文字(またはハッシュ)でまず候補を絞るコードを生成するので、case が増えてもコストがそのぶん増えません。
結局、選ぶ基準はほとんど読みやすさです。
| 場面 | 選ぶもの |
|---|---|
| 条件ごとに別の変数を調べる、範囲と別の検査が混ざる | if / else if |
| 1つの値、ケースが多く、ケースごとに文を実行 | switch 文 |
| 1つの値(またはタプル)、ケースごとに値を返すだけ | switch 式 |
| 頻繁に実行されるコードで文字列キーが多い | switch(キーがコードではなくデータなら Dictionary) |
| すべてのケースを漏れなく処理したい | switch 式 + <WarningsAsErrors>CS8509</WarningsAsErrors> |
条件式で使う関係・論理演算子は C#の演算子、switch の対象になる型は C#の変数・データ型・定数 で扱っています。_ のアームで意味のある例外を投げる方法は C#の例外処理 を、switch 式と相性のよい ラムダ式 もあわせてどうぞ。
検証環境:.NET SDK 10.0.403 / ランタイム 10.0.14、C# 14(net10.0 の既定)、macOS、Apple Silicon Mac(M1 Pro)。
よくある質問
C#の警告 CS8509 はどういう意味?
switch 式がすべての入力を処理していない、という警告です。コンパイラは「パターン 0 が処理されていない」のように漏れている値を1つ示します。警告なのでビルドは通りますが、実行中にその値が来ると System.Runtime.CompilerServices.SwitchExpressionException が発生します。漏れているアームか _ => ... のアームを足すか、<WarningsAsErrors>CS8509</WarningsAsErrors> でビルドエラーにできます。
C#では switch のほうが if-else より速い?
場合によります。.NET 10(Release、Apple Silicon Mac)で2,000万回分岐させると、int の10ケースは if の連鎖 133ms、switch 139ms、文字列7ケースは224ms対217msで誤差の範囲でした。文字列キーが30個のときだけ switch 291ms、if の連鎖 413ms とはっきり速くなりました。文字列のケースが多くなければ、読みやすいほうを選べば十分です。
C#の switch は整数・文字・文字列にしか使えない?
それは C# 7 より前の話です。C# 7 から switch はパターンでどんな型でも調べられ、C# 9 からは double に case < 35.0 のような関係パターンも書けます。この記事の2021年版は古いルールのままでした。