DartのSwitch式/文、when句、パターンマッチング周りのチートシート

2026/01/16 00:27公開
2026/01/17 13:06最終更新
Table of Contents
  1. 🔹 基本構文
    1. Switch文(Statement)
    2. Switch式(Expression)
  2. 🔹 基本的な使い方
    1. 1. シンプルなcase文/式
    2. 2. 型パターンマッチング
  3. 🔹 when句(ガード条件)
    1. 基本構文
    2. 例:オブジェクトパターン(後述)を使った場合分け
  4. 🔹 パターンマッチング
    1. 1. リストパターン
    2. 2. レコードパターン
    3. 3. オブジェクトパターン
  5. if文でのパターンマッチングとnullチェック
    1. 基本的な構文
    2. 使用例
  6. 🔹 Switch式 vs Switch文
  7. ✅ ワイルドカード _ の意味
  8. 🔹 Switch式における複数条件の指定
    1. 1. 論理和・論理積(OR/AND)
    2. 2. 関係演算子パターン - 範囲指定
    3. 3. 型パターンと条件の組み合わせ
    4. 4. リストパターンでの複数条件
    5. 5. マップパターンでの複数条件
    6. 6. レコードパターンでの複数条件
    7. 7. オブジェクトパターンでの複数条件
    8. 8. 複数の変数を組み合わせた条件
    9. 9. 実用的な複雑な例
    10. 10. 入れ子になったパターン
  9. 最後に
  10. 📚 参考リンク

🔹 基本構文

Switch文(Statement)

switch (値) {
  case パターン: 
    // 処理
  case パターン when 条件式: 
    // ガード条件付き処理
  default: 
    // デフォルト処理
}

breakは不要

Switch式(Expression)

final result = switch (値) {
  パターン => 結果,
  パターン when 条件式 => 結果,
  _ => デフォルト結果,
};

🔹 基本的な使い方

1. シンプルなcase文/式

// Switch文
switch (color) {
  case 'red':
    print('赤色です');
  case 'blue':
    print('青色です');
  default:
    print('その他の色です');
}

// Switch式
final colorName = switch (color) {
  'red' => '赤色',
  'blue' => '青色',
  _ => 'その他の色',
};

2. 型パターンマッチング

// Switch文
switch (value) {
  case int n:
    print('整数:  $n');
  case String s:
    print('文字列: $s');
  case double d:
    print('浮動小数点数: $d');
  default:
    print('その他の型');
}

// Switch式
final description = switch (value) {
  int n => '整数: $n',
  String s => '文字列: $s',
  double d => '浮動小数点数: $d',
  _ => 'その他の型',
};

🔹 when句(ガード条件)

when句を使うことで、パターンマッチに加えて追加の条件を指定できる。

基本構文

case パターン when 条件式:

例:オブジェクトパターン(後述)を使った場合分け

// Switch文
switch (snapshot) {
  case AsyncData(:final value) when value.score >= 80:
    print('優秀');
  case AsyncData(:final value) when value.score >= 60:
    print('合格');
  case AsyncData(:final value) when value.score >= 0:
    print('不合格');
  case AsyncData(:final value):
    print('無効なスコア');
  case AsyncLoading():
    print('取得中');
  case AsyncError(:final error):
    print('スコア取得失敗: $error');
}

// Switch式
final grade = switch (snapshot) {
  AsyncData(:final value) when value.score >= 80 => '優秀',
  AsyncData(:final value) when value.score >= 60 => '合格',
  AsyncData(:final value) when value.score >= 0 => '不合格',
  AsyncData(:final value) => '無効なスコア',
  AsyncLoading() => '取得中',
  AsyncError(:final error) => 'スコア取得失敗: $error',
};

🔹 パターンマッチング

final 変数名の部分は変数を定義するときと同じ書き方ができ、再代入を許可する場合はvarとすることも可能。

1. リストパターン

switch (data) {
  case [int a, int b] when a == b:
    print('同じ値の2つの整数');
  case [int a, int b]:
    print('異なる値の2つの整数:  $a と $b');
  case [int single]: 
    print('1つの整数:  $single');
  case []:
    print('空のリスト');
  default:
    print('その他の構造');
}

2. レコードパターン

final point = (x: 10, y: 20);

final quadrant = switch (point) {
  (x: final x, y: final y) when x > 0 && y > 0 => '第1象限',
  (x: final x, y: final y) when x < 0 && y > 0 => '第2象限',
  (x: final x, y: final y) when x < 0 && y < 0 => '第3象限',
  (x: final x, y: final y) when x > 0 && y < 0 => '第4象限',
  _ => '原点または軸上',
};

3. オブジェクトパターン

switch (user) {
  case User(name: 'admin', age: final age):
    print('管理者(年齢:  $age)');
  case User(name: final name, age: final age) when age < 18:
    print('未成年ユーザー:  $name');
  // スコープ内の変数とオブジェクトのプロパティが同名の場合、省略可
  case User(:final name, :final age) when age >= 65:
    print('シニアユーザー: $name');
  default:
    print('一般ユーザー');
}

if文でのパターンマッチングとnullチェック

if文でもcase句を使用することで、同様にパターンマッチングを使用することが出来る。

基本的な構文

if (値 case パターン) {}
if (値 case パターン when 条件) {}

使用例

if (user case SuperUser(:final name)) {
  print("こんにちは、$nameさん。");
}

// whenを使用した例
if (user case SuperUser(:final name, :final age) when age >= 30) {
  print("こんにちは、$nameさん(30歳以上)。");
}

これは何に使うと便利かというと、単純なnullチェックで昇格できない、オブジェクトのプロパティなどの 非nullを保証できる

// context.userがnullだった場合、if文の中身は実行されない
if (context.user case final user?) {
  print(value.length); // value は String (非null型)
}

// textがnullだった場合、if文に入る前にエラーを投げる
if (context.user case final user!) {
  print(value.length); // value は String (非null型)
}

🔹 Switch式 vs Switch文

特徴 Switch式(Expression) Switch文(Statement)
戻り値 必ず値を返す 値を返さない
構文 条件 => 結果 case 条件: 処理
デフォルト _ default:
セミコロン 必要 不要
用途 変数への代入 処理の実行
// Switch式:値を返す
final result = switch (value) {
  1 => 'one',
  2 => 'two',
  _ => 'other',
}; // セミコロンが必要

// Switch文:処理を実行
switch (value) {
  case 1:
    print('one');
  case 2:
    print('two');
  default:
    print('other');
} // セミコロン不要

✅ ワイルドカード `_` の意味

  • Switch式 の中で使用すると・・・すべてにマッチするデフォルトケース
  • パターン内 で使用すると・・・値を無視する(変数に代入しない)
final result = switch (value) {
  int _ => '何らかの整数',  // どの整数でもマッチ、値を使わない
  _ => 'その他',           // すべてにマッチ
};

🔹 Switch式における複数条件の指定

※上に書いた条件から順に評価される。

1. 論理和・論理積(OR/AND)

// 論理和(OR)
String getDayType(String day) => switch (day) {
  'Monday' || 'Tuesday' || 'Wednesday' || 'Thursday' || 'Friday' => '平日',
  'Saturday' || 'Sunday' => '週末',
  _ => '不明',
};

// 論理積(AND)※単に&&を使った指定も可能
String getDiscount(int age, bool isMember) => switch ((age, isMember)) {
  (>= 65, true) => 'シニア会員割引:  30%オフ',
  (>= 65, false) => 'シニア割引: 15%オフ',
  (< 18, true) => '学生会員割引: 20%オフ',
  (_, true) => '会員割引: 10%オフ',
  _ => '割引なし',
};

2. 関係演算子パターン - 範囲指定

// 範囲による分類
String getGrade(int score) => switch (score) {
  >= 90 => 'A',
  >= 80 => 'B',
  >= 70 => 'C',
  >= 60 => 'D',
  >= 0 => 'F',
  _ => '無効なスコア',
};

// 複数の関係演算子を組み合わせ
String getAgeGroup(int age) => switch (age) {
  < 0 => '無効',
  >= 0 && < 13 => '子供',
  >= 13 && < 20 => '10代',
  >= 20 && < 65 => '成人',
  >= 65 => 'シニア',
  _ => '不明',
};

3. 型パターンと条件の組み合わせ

// 型と値の条件を組み合わせ
String describe(Object value) => switch (value) {
  int n when n > 0 => '正の整数:  $n',
  int n when n < 0 => '負の整数: $n',
  int _ => 'ゼロ',
  double d when d > 0.0 => '正の浮動小数点数: $d',
  double d when d < 0.0 => '負の浮動小数点数: $d',
  double _ => 'ゼロ',
  String s when s.isEmpty => '空文字列',
  String s => '文字列:  $s',
  _ => 'その他の型',
};

// null と型チェックの組み合わせ
String handleValue(Object? value) => switch (value) {
  // 値がnullの場合
  null => 'null値',

  // 整数の型チェック + マッチング
  int n => '整数値: $n',

  // その他の型
  double d when d.isFinite => '小数値: $d',
  String s => '文字列: $s',

  // 上記に該当しないその他すべて
  _ => 'その他',
};

4. リストパターンでの複数条件

// リストの要素数と内容で分岐
String analyzeList(List<int> numbers) => switch (numbers) {
  [] => '空のリスト',
  [_] => '要素1つ',
  [final a, final b] when a == b => '同じ値の2要素:  $a',
  [final a, final b] when a < b => '昇順の2要素: $a, $b',
  [final a, final b] => '降順の2要素:  $a, $b',
  [final first, ..., final last] when first == last => '最初と最後が同じ',
  [final first, ... ] when first > 0 => '最初が正の数のリスト',
  _ => 'その他のリスト',
};

// 特定のパターンにマッチ
String matchCoordinates(List<num> coords) => switch (coords) {
  [0, 0] => '原点',
  [final x, 0] => 'X軸上の点:  ($x, 0)',
  [0, final y] => 'Y軸上の点: (0, $y)',
  [final x, final y] when x > 0 && y > 0 => '第1象限',
  [final x, final y] when x < 0 && y > 0 => '第2象限',
  [final x, final y] when x < 0 && y < 0 => '第3象限',
  [final x, final y] when x > 0 && y < 0 => '第4象限',
  _ => 'その他',
};

5. マップパターンでの複数条件

// マップのキーと値で複数条件
String processRequest(Map<String, dynamic> request) => switch (request) {
  {'method': 'GET', 'path': '/api/users'} => 'ユーザー一覧を取得',
  {'method': 'GET', 'path': String path} when path.startsWith('/api/users/') => 
    'ユーザー詳細を取得',
  {'method': 'POST', 'path': '/api/users', 'body': Map body} => 
    'ユーザーを作成',
  {'method': 'PUT', 'path': String path, 'body': Map body} 
    when path.startsWith('/api/users/') => 'ユーザーを更新',
  {'method': 'DELETE', 'path': String path} 
    when path.startsWith('/api/users/') => 'ユーザーを削除',
  {'method': String method} => '不明なエンドポイント:  $method',
  _ => '無効なリクエスト',
};

// 複数のキーの組み合わせで判定
String getUserStatus(Map<String, dynamic> user) => switch (user) {
  {'isActive': true, 'isPremium': true, 'role': 'admin'} => 
    'プレミアム管理者',
  {'isActive': true, 'isPremium': true} => 
    'プレミアムユーザー',
  {'isActive': true, 'role': 'admin'} => 
    '管理者',
  {'isActive': true} => 
    '通常ユーザー',
  {'isActive': false} => 
    '無効なユーザー',
  _ => 
    '不明',
};

6. レコードパターンでの複数条件

// レコードの値で複数条件
String analyzePoint((int x, int y) point) => switch (point) {
  (0, 0) => '原点',
  (final x, 0) => 'X軸上',
  (0, final y) => 'Y軸上',
  (final x, final y) when x == y => '対角線上',
  (final x, final y) when x == -y => '逆対角線上',
  (final x, final y) when x > 0 && y > 0 => '第1象限',
  (final x, final y) when x < 0 && y > 0 => '第2象限',
  (final x, final y) when x < 0 && y < 0 => '第3象限',
  (final x, final y) when x > 0 && y < 0 => '第4象限',
  _ => 'その他',
};

// 名前付きレコード
String analyzeUser((String name, int age, {bool isPremium}) user) => 
  switch (user) {
    (name: 'admin', age: _, isPremium: _) => '管理者アカウント',
    (name: _, age: < 18, isPremium: _) => '未成年ユーザー',
    (name: _, age: >= 65, isPremium: true) => 'シニアプレミアムユーザー',
    (name: _, age: _, isPremium: true) => 'プレミアムユーザー',
    (name: final name, age: final age, isPremium: false) => 
      '通常ユーザー: $name ($age歳)',
    _ => '不明',
  };

7. オブジェクトパターンでの複数条件

sealed class Shape {}
class Circle extends Shape {
  final double radius;
  Circle(this.radius);
}
class Rectangle extends Shape {
  final double width, height;
  Rectangle(this. width, this.height);
}
class Triangle extends Shape {
  final double base, height;
  Triangle(this.base, this. height);
}

// オブジェクトの型とプロパティで複数条件
double calculateArea(Shape shape) => switch (shape) {
  Circle(radius: final r) when r <= 0 => 0,
  Circle(radius: final r) => 3.14 * r * r,
  Rectangle(width: final w, height:  final h) when w <= 0 || h <= 0 => 0,
  Rectangle(width: final w, height: final h) => w * h,
  Triangle(base: final b, height: final h) when b <= 0 || h <= 0 => 0,
  Triangle(base:  final b, height: final h) => b * h / 2,
};

// 複雑な条件
String describeShape(Shape shape) => switch (shape) {
  Circle(radius: final r) when r > 10 => '大きな円',
  Circle(radius: final r) when r > 5 => '中くらいの円',
  Circle(radius: final r) when r > 0 => '小さな円',
  Rectangle(width: final w, height: final h) when w == h => '正方形',
  Rectangle(width: final w, height: final h) when w > h => '横長の長方形',
  Rectangle(width: final w, height: final h) when w < h => '縦長の長方形',
  Triangle(base: final b, height: final h) when b == h => '底辺と高さが同じ三角形',
  _ => 'その他の図形',
};

8. 複数の変数を組み合わせた条件

// タプル(レコード)を使った複数変数の判定
String checkCredentials(String username, String password) => 
  switch ((username, password)) {
    ('', _) => 'ユーザー名が空です',
    (_, '') => 'パスワードが空です',
    ('admin', 'admin123') => '管理者としてログイン',
    (final user, final pass) when user. length < 3 => 
      'ユーザー名が短すぎます',
    (final user, final pass) when pass.length < 8 => 
      'パスワードが短すぎます',
    (final user, _) when user.contains('@') => 
      'メールアドレスでログイン',
    _ => '通常ログイン',
  };

// 複数パラメータの組み合わせ
String determineShipping(double weight, String destination, bool express) => 
  switch ((weight, destination, express)) {
    (_, _, true) when weight > 30 => '特急便(重量物)',
    (_, _, true) => '特急便',
    (> 30, 'domestic', false) => '国内便(重量物)',
    (<= 30, 'domestic', false) => '国内便(通常)',
    (> 30, 'international', false) => '国際便(重量物)',
    (<= 30, 'international', false) => '国際便(通常)',
    _ => '配送方法を確認してください',
  };

9. 実用的な複雑な例

// HTTPレスポンスの処理
String handleResponse(int statusCode, Map<String, dynamic>? body) => 
  switch ((statusCode, body)) {
    (200, {'data': List data}) when data.isNotEmpty => 
      '成功:  ${data.length}件のデータ',
    (200, {'data': List data}) when data.isEmpty => 
      '成功: データなし',
    (201, {'id': int id}) => 
      '作成成功: ID $id',
    (400, {'error': String msg}) => 
      'リクエストエラー: $msg',
    (401, _) => 
      '認証エラー',
    (403, _) => 
      '権限エラー',
    (404, _) => 
      '見つかりません',
    (>= 500, _) => 
      'サーバーエラー',
    _ => 
      '不明なレスポンス',
  };

// ファイル処理の判定
String getFileAction(String extension, int size, bool isReadOnly) => 
  switch ((extension. toLowerCase(), size, isReadOnly)) {
    (_, _, true) => 'ファイルは読み取り専用です',
    ('exe' || 'bat' || 'sh', _, _) => '実行ファイルです',
    ('jpg' || 'png' || 'gif', > 10000000, _) => 
      '大きな画像ファイル(${(size / 1000000).toStringAsFixed(1)}MB)',
    ('jpg' || 'png' || 'gif', _, _) => 
      '画像ファイル',
    ('mp4' || 'avi' || 'mov', _, _) => 
      '動画ファイル',
    ('txt' || 'md', < 1000, _) => 
      '小さなテキストファイル',
    ('txt' || 'md', _, _) => 
      'テキストファイル',
    (_, > 100000000, _) => 
      '非常に大きなファイル(${(size / 1000000).toStringAsFixed(1)}MB)',
    _ => 
      '一般ファイル(.$extension)',
  };

10. 入れ子になったパターン

// 複雑な入れ子構造
String analyzeData(dynamic data) => switch (data) {
  // リストの中にマップがある場合
  [{'type': 'user', 'name': String name}] => 
    '単一ユーザー:  $name',
  
  // 複数要素でパターンマッチ
  [{'type': 'user', 'name': String name}, ... ] when data.length > 5 => 
    '多数のユーザー(${data.length}人)',
  
  // マップの中にリストがある場合
  {'users': List users, 'total': int total} when users.length == total => 
    'ユーザーリスト完全:  $total人',
  
  {'users': List users, 'total': int total} when users.length < total => 
    'ユーザーリスト部分的: ${users.length}/$total人',
  
  // 深い入れ子
  {'data': {'user': {'name': String name, 'age': int age}}} 
    when age >= 18 => '成人ユーザー:  $name',
  
  _ => 'その他のデータ構造',
};

最後に

このチートシートは、Dart 3以降のモダンなswitch構文に対応している。独特な構文で慣れが必要だが、パターンマッチングとガード条件(when句)を活用することで、より表現力豊かで読みやすいコードが書けるようになるはず。

📚 参考リンク

※本記事は、GitHub Copilot「Claude Sonnet 4.5」を利用して出力したものを加筆修正したものです。