C#での開発において、条件分岐はプログラムの論理構造を決定する極めて重要な要素です。
多くの場合、if文を使用することで事足りますが、分岐の数が増えたり、特定の変数や式の値に基づいて処理を振り分けたりする場合、switch文やswitch式を利用することで、コードの可読性とメンテナンス性を劇的に向上させることができます。
現代のC#では、従来の単純な値の比較にとどまらず、パターンマッチングと呼ばれる強力な機能が統合されており、複雑なデータ構造に対しても簡潔に記述できるようになりました。
本記事では、初心者の方が押さえておくべき基本の「switch文」から、C# 8.0以降で主流となった「switch式」、そして最新のパターンマッチング機能までを網羅的に解説します。
switch文の基本構造と動作
C#におけるswitch文は、指定した式の値に基づいて、複数の実行パスの中から1つを選択するための制御構造です。
if-else if-elseの連鎖をより分かりやすく記述できるため、特定の値に対する多分岐処理に非常に適しています。
基本的な構文とキーワード
switch文を構成する主な要素は、switch、case、break、そしてdefaultです。
using System;
class Program
{
static void Main()
{
int dayOfWeek = 3;
// switch文の基本形
switch (dayOfWeek)
{
case 1:
Console.WriteLine("月曜日です");
break; // 処理の終了を明示
case 2:
Console.WriteLine("火曜日です");
break;
case 3:
Console.WriteLine("水曜日です");
break;
default:
// どのcaseにも該当しない場合の処理
Console.WriteLine("週の後半、または不正な値です");
break;
}
}
}
水曜日です
この例では、dayOfWeek変数の値が「3」であるため、case 3:のブロックが実行されます。
breakキーワードの重要性
C#のswitch文において、break文は原則として省略できません。
C++などの一部の言語では、breakを書かないと次のcaseへ処理が流れる「フォールスルー」が発生しますが、C#ではコンパイルエラーとなります(ただし、caseラベルを連続して記述し、処理が空の場合は例外的に認められます)。
これにより、意図しないバグの混入を防いでいます。
defaultラベルの役割
defaultラベルは、評価対象の値がいずれのcaseにも一致しなかった場合に実行されるセクションです。
必須ではありませんが、予期しない値が渡された際の例外処理やログ出力のために記述しておくことが推奨されます。
複数の条件をまとめる記述
同じ処理を複数の値に対して実行したい場合は、caseラベルを並べて記述します。
using System;
class Program
{
static void Main()
{
string fruit = "Apple";
switch (fruit)
{
case "Apple":
case "Cherry":
case "Strawberry":
// Apple, Cherry, Strawberryのいずれかであれば実行される
Console.WriteLine("これは赤い果物です。");
break;
case "Banana":
case "Lemon":
Console.WriteLine("これは黄色い果物です。");
break;
default:
Console.WriteLine("色が特定できません。");
break;
}
}
}
これは赤い果物です。
このように、処理の中身を持たないcaseを並べることで、論理的なOR条件として機能させることができます。
switch式の登場とモダンな記述方法
C# 8.0から導入されたswitch式は、従来のswitch文をより簡潔に、かつ「式」として扱えるようにしたものです。
switch「文」が処理の流れを制御するのに対し、switch「式」は値を返すことに特化しています。
switch式の基本構文
switch式では、キーワードの配置が従来のswitch文とは逆になり、=>(ラムダ演算子に似た記法)を使用します。
using System;
class Program
{
static void Main()
{
int level = 2;
// switch式による値の割り当て
string rank = level switch
{
1 => "Bronze",
2 => "Silver",
3 => "Gold",
_ => "Unknown" // デフォルトケース(破棄パターン)
};
Console.WriteLine($"あなたのランクは {rank} です。");
}
}
あなたのランクは Silver です。
switch式の特徴とメリット
switch式には、従来のswitch文にはないいくつかの特徴があります。
- 簡潔なコード
従来の
switch文のようにcaseやbreakを書く必要がなく、非常に スッキリとした見た目 になります。該当するケースに対する処理を直接式として記述できます。
- 代入に最適
式の評価結果をそのまま変数に代入したり、メソッドの戻り値として返したりできます(例:
var result = switchExpr;のように使用)。- 破棄パターンの利用
未使用の分岐や値を扱う際に、
defaultキーワードの代わりに_(アンダースコア)を使用できます。これを破棄パターンと呼びます。
- 網羅性のチェック
switch式では、すべての可能性(すべての入力値)を考慮していない場合にコンパイラが警告を出すことがあります。
特に列挙型(enum)を使用している場合は有用です。
switch式は、「特定の入力に対して1つの出力を決める」という単純なマッピングにおいて非常に強力なツールとなります。
パターンマッチングによる高度な条件分岐
C#のswitch機能が真価を発揮するのは、パターンマッチングと組み合わせたときです。
単なる値の比較を超えて、型、プロパティの状態、複数の値の組み合わせなどを条件に含めることができます。
型パターン(Declaration Pattern)
変数の型に基づいて分岐し、同時にその型としてキャストされた変数を利用できます。
public static void PrintShapeInfo(object shape)
{
switch (shape)
{
case Circle c:
Console.WriteLine($"円:半径は {c.Radius}");
break;
case Rectangle r:
Console.WriteLine($"長方形:面積は {r.Width * r.Height}");
break;
case null:
Console.WriteLine("オブジェクトが null です");
break;
default:
Console.WriteLine("未知の図形です");
break;
}
}
この例では、shapeがCircle型であれば、変数cとしてその場で利用可能です。
これは、従来のis演算子によるチェックとキャストを一度に行っていることになります。
リレーショナルパターンと論理パターン
C# 9.0以降では、数値の範囲などを直感的に記述できるリレーショナルパターン(比較演算子)や、and、or、notといった論理パターンが利用可能になりました。
static string GetTemperatureMessage(int temp) => temp switch
{
< 0 => "氷点下です。非常に寒いです。",
>= 0 and < 15 => "少し肌寒いですね。",
>= 15 and < 25 => "過ごしやすい気温です。",
>= 25 and not 100 => "暑くなってきました。",
100 => "沸点です!",
_ => "測定不能"
};
if文を連ねるよりも、条件の範囲が視覚的に整理されていることがわかります。
特にandを用いた範囲指定は、プログラムの意図を正確に伝えます。
プロパティパターン
オブジェクトが持つ特定のプロパティの値を条件に指定することも可能です。
public record Order(int Quantity, double Price, string Category);
public static double GetDiscount(Order order) => order switch
{
{ Category: "Food", Quantity: > 10 } => 0.2, // 食品かつ10個より多い
{ Category: "Food" } => 0.1, // 食品全般
{ Quantity: > 100 } => 0.15, // カテゴリ問わず100個超
_ => 0.0
};
{ PropertyName: pattern } という形式で記述します。
複数のプロパティを組み合わせることもでき、非常に複雑なビジネスロジックを簡潔に表現できます。
switch文で使える便利な機能とテクニック
基本的な使い方以外にも、実務で役立つ応用的なテクニックがいくつか存在します。
when句による追加条件(ガード句)
caseラベルにさらに詳細な条件を付け加えたい場合は、whenキーワードを使用します。
int number = -5;
switch (number)
{
case int n when n < 0:
Console.WriteLine($"{n} は負の数です。");
break;
case int n when n > 0:
Console.WriteLine($"{n} は正の数です。");
break;
default:
Console.WriteLine("ゼロです。");
break;
}
when句を使用することで、同じ型や同じ値であっても、実行時の動的な条件(他の変数の状態など)を組み込んで分岐させることができます。
タプルパターン(複数の値の組み合わせ)
複数の変数の組み合わせに基づいて分岐させたい場合、タプルを利用すると非常にスマートです。
string GetResult(string state, string action) => (state, action) switch
{
("Locked", "Push") => "動かない",
("Locked", "Unlock") => "解錠された",
("Open", "Close") => "閉まった",
("Open", "Push") => "さらに開いた",
_ => "不明な操作"
};
複数の変数を個別にif文で判定するとネスト(入れ子)が深くなりがちですが、タプルパターンを使えば「状態の遷移表」をそのままコードにしたような直感的な記述が可能になります。
switch文とswitch式の使い分けと注意点
非常に便利なswitchですが、何でもswitchで書けば良いというわけではありません。
if文との使い分けや、パフォーマンス、注意点についても理解しておきましょう。
if文とswitch文のどちらを使うべきか
一般的に、以下のような基準で選択します。
- if文が適している場合
true/falseのようなブール条件のみを評価する場合。- 全く異なる種類の複数の条件を組み合わせる必要がある場合。
- 分岐が2つ(trueかfalseか)のみである場合。
- switch文/式が適している場合
- 1つの変数や式を多数の固定値や型と比較する場合(複数のケースを持つとき)。
- 数値の範囲ごとに処理を分けたい場合(C# 9.0以降のパターンマッチを利用)。
- 条件分岐の結果として値を1つ返したい場合(
switch式)。
パフォーマンスに関する考察
C#のswitch文は、条件の数が多い場合、コンパイラによって最適化されます。
具体的には、値の範囲が詰まっている場合は「ジャンプテーブル」が作成され、値が分散している場合は「ハッシュテーブル」や「二分探索」に近いロジックが生成されることがあります。
一方、if-else ifの連続は、上から順番に条件を評価していくため、条件の数に比例して処理時間が増える可能性があります。
ただし、現代のコンピュータでは微々たる差であることも多いため、基本的には「読みやすさ」を最優先に選択するのがベストです。
実行時の注意点:Null許容型
switch文で参照型を扱う際、対象のオブジェクトが null である可能性を考慮する必要があります。
string name = null;
switch (name)
{
case "Alice":
// ...
break;
case null:
Console.WriteLine("名前が設定されていません。");
break;
default:
// ...
break;
}
C#のswitch文はnullを正しく扱えますが、switch式などでnullを考慮し忘れると、実行時に例外が発生したり、意図しない_(デフォルトケース)に流れたりすることがあります。
パターンマッチングを行う際は、常にnullの可能性を意識しましょう。
まとめ
C#のswitch文とswitch式は、言語の進化とともに非常に強力な機能へと変貌を遂げました。
- switch文は、複数の処理ブロックを明確に分ける際の古典的かつ確実な方法です。
- switch式は、C# 8.0以降の標準的な書き方であり、値を返す処理を簡潔に記述できます。
- パターンマッチングを活用することで、型、範囲、プロパティ、タプルなど、あらゆる条件を宣言的に表現できます。
これらの機能を使いこなすことで、複雑な条件分岐も「何をしているのか」が一目でわかる、美しく堅牢なコードになります。
まずは基本的な値の比較から始め、徐々にリレーショナルパターンやswitch式を取り入れて、モダンなC#プログラミングを実践してみてください。






