分岐制御フロー

一、if / if-else 基本分岐

  1. if 丸括弧の中身は必ず真偽値式にする必要があり、暗黙的な真偽型への変換は許可されません
  2. else if を複数連ねて記述でき、最後に任意で else を追加可能
  3. 処理が1行だけの場合は波括弧を省略できますが、可読性のため全部括弧を付ける習慣が推奨されます
void main() {
  bool isRaining = false;
  bool isSnowing = true;

  if (isRaining) {
    print("レインコートを持っていく");
  } else if (isSnowing) {
    print("厚手の上着を着る");
  } else {
    print("オープンカーでドライブ");
  }
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
厚手の上着を着るCode language: CSS (css)

if 単体だけで使用することもできます

void main() {
  bool isRaining = false;
  bool isSnowing = true;

  if (isRaining) {
    print("レインコートを持っていく");
  }
}Code language: Dart (dart)

あるいは if else だけの記述も可能

void main() {
  bool isRaining = false;
  bool isSnowing = true;

  if (isRaining) {
    print("レインコートを持っていく");
  } else {
    print("オープンカーでドライブ");
  }
}Code language: Dart (dart)

二、if-case パターンマッチ分岐(Dart3.0以降対応)

  1. パターンに適合した場合、変数をデストラクチャリングして現在のスコープに束縛します
  2. マッチに失敗したら else へ移行。when ガードを追加して追加の真偽判定を書けます
  3. 単一の値に単一パターンを照合する構文のため、複数パターンの分岐には switch を使います
void main() {
  final pair = [100, 220];
  // 要素2つ、両方int型の配列にマッチ
  if (pair case [int x, int y]) {
    print("座標:$x,$y");
  } else {
    throw FormatException("不正な座標");
  }
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
座標:10,20Code language: CSS (css)

2:when ガードと組み合わせた if-case

void main() {
  final data = [8, 9];
  if (data case [int a, int b] when a > 5 && b > 5) {
    print("二つの数が両方5より大きい");
  } else {
    print("数値が条件を満たしていない");
  }
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
二つの数が両方5より大きいCode language: CSS (css)

三、switch 文

従来型のswitchは文形式で、戻り値を持ちません

  1. caseには文字列、定数、論理OR、関係演算子パターンなど任意のパターンを指定可能
  2. 中身のあるcaseは自動的にbreakするため手動でbreakを書く必要なし。空のcaseは自動でフォールスルーします
  3. 手動でフォールスルーさせる方法は2種類:continue ラベル / 空のcaseを連続記述
  4. どのcaseにも一致しなかった場合 default が実行。when ガードに対応
1:基本的な文字列マッチ
void main() {
  String command = "OPEN";
  switch (command) {
    case "CLOSED":
      print("クローズ処理を実行");
    case "PENDING":
      print("待機処理を実行");
    case "OPEN":
      print("オープン処理を実行");
    default:
      print("不明なコマンド");
  }
}
Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
オープン処理を実行Code language: CSS (css)

2:空caseによるフォールスルー + continueラベルでのジャンプ

String command = "OPEN";
switch (command) {
  case "OPEN":
    print("オープン操作"); // ① OPENにマッチし出力
    continue newCase; // ② newCaseラベルへ直接ジャンプ、戻らない

  case "DENIED": 
  case "CLOSED":
    print("クローズ操作");

  newCase: // ③ ジャンプ先ラベル
  case "PENDING":
    print("共通の保留処理"); // ④ command == "PENDING" の判定をスキップして即実行
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
オープン操作
共通の保留処理Code language: CSS (css)

この例で continue newCase; はnewCaseラベルへ飛ぶとしても、直後のcase “PENDING”にはマッチしないはずなのに、なぜ print(“共通の保留処理”); が実行されるのでしょうか?

多くの読者が抱く疑問点です

continue ラベル; は値を再照合せず、ラベルのあるcase分岐の入口へ直接ジャンプし、条件判定を省略して分岐内コードを即実行します

3:switch + when ガード

void main() {
  int score = 88;
  switch (score) {
    case int s when s >= 90:
      print("優秀");
    case int s when s >= 60:
      print("合格");
    default:
      print("不合格");
  }
}
Code language: Dart (dart)

出力:合格

まず型がintか確認し、intならsに値を代入。その後whenガードで2次判定を行い、条件を満たした場合のみcase内の文が実行されます。ここの二箇所のint sはそれぞれスコープが独立し干渉しません。

case int s独立したパターン束縛で、変数の有効範囲は自身のcase内だけに限定されます

二つのcaseで別々の変数名を使っても動作は完全に同じです。

switch (score) {
  case int a when a >= 90:
    print("優秀");
  case int b when b >= 60:
    print("合格");
  default:
    print("不合格");
}Code language: Dart (dart)

四、switch 式

Dart3.0以降で利用可能な構文で、戻り値を持ち変数代入によく使われます

switch文との違い

  1. caseキーワードがなく、=> でパターンと式を区切る
  2. 各分岐は必ず式を返し、分岐同士をカンマで区切る
  3. 暗黙的なフォールスルーはなく各分岐が独立。一致するものがない場合はアンダースコア _ を使用、defaultは使えない
  4. 代入、print、returnなどあらゆる式コンテキストで直接利用可能

1:基本文字演算子マッチ(論理ORパターン)

void main() {
  const slash = 47, star = 42, comma = 44;
  int charCode = 42;

  final token = switch (charCode) {
    slash || star => "演算子",
    comma => "句読点",
    _ => "無効文字"
  };
  print(token);
}
Code language: Dart (dart)

実行結果

演算子

このサンプルでは3種類の記号のASCIIコードを定数として定義し、charCode変数の値が演算子・句読点・その他のどれに該当するか判定しています。

2:switch式 + whenガード

void main() {
  int age = 22;
  String level = switch (age) {
    int a when a < 18 => "未成年",
    int a when a < 30 => "青年",
    _ => "成人"
  };
  print(level);
}Code language: Dart (dart)

実行結果

青年

3:関数のreturn内でswitch式を直接使用

void main() {
  print(getDesc(3));
}

String getDesc(int num) => switch (num) {
  1 => "一",
  2 => "二",
  _ => "その他の数字"
};
Code language: Dart (dart)

実行結果

その他の数字

補足:ここのswitchは式であり、getDescはアロー関数です。アロー関数は他のC系言語にはないDart独自の文法です。

五、網羅性チェック Exhaustiveness Checking

入力値に対応する分岐が不足しているとコンパイルエラーが発生し、不足したcaseを補完するよう強制されます。sealedクラス・enum列挙型はデフォルトで網羅チェックに対応し、_ やdefaultによるフォールバックが不要です。

1:Nullable真偽値による網羅漏れエラー(コメントアウトして実行するとエラーを確認可能)

void main() {
  bool? nullableBool = null;
  // 下記コードのコメントを外すとコンパイルエラー:nullに対応するcaseがなく、_/defaultによる補完もないため
  /*
  switch (nullableBool) {
    case true:
      print("真");
    case false:
      print("偽");
  }
  */
  // 修正方法:null用caseを追加、または_で補完
  switch (nullableBool) {
    case true:
      print("真");
    case false:
      print("偽");
    case null:
      print("null値");
  }
}Code language: PHP (php)

実行結果

null値

2:sealed 密封クラス

代数的データ型で、完全な網羅マッチを強制します

import 'dart:math' as math;

// sealedクラス:サブクラスは同一ファイル内でのみ定義可能
sealed class Shape {}

// サブクラス 正方形:辺の長さ
class Square implements Shape {
  final double length;
  Square(this.length);
}

//サブクラス 円:半径
class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

// 渡されたサブクラスオブジェクトをswitchで判定し面積を計算するアロー関数
double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => math.pi * r * r
  // Shapeに新しいサブクラスを追加すると、対応するcaseが不足しコンパイルエラーになる
};

void main() {
  final Shape circle = Circle(5);
  print(calculateArea(circle));
  final Shape square = Square(4);
  print(calculateArea(square));
}
Code language: Dart (dart)

実行結果

78.53981633974483
16.0Code language: CSS (css)

3:Enum列挙型の網羅マッチ

フォールバック用のアンダースコア_ が不要

void main() {
  final Animal animal = Animal.cat;
  String name = switch (animal) {
    Animal.dog => "犬",
    Animal.cat => "猫"
  };
  print(name);
}

enum Animal { dog, cat }
Code language: Dart (dart)

出力:猫

enumは値の範囲が固定され未知の値が発生しないため、フォールバック分岐を用意する必要がありません。

六、when ガード

if-case / switch文 / switch式の全構文で利用可能

whenの役割:パターンマッチ成功後に追加の真偽判定を実行。ガードの結果がfalseの場合、switchを抜けず次の分岐へ進みます。

1. if-case + when

void main() {
  List<int> arr = [10, 20];
  if (arr case [int x, int y] when x + y > 20) {
    print("二数の合計が20を超える");
  }
}Code language: Dart (dart)
二数の合計が20を超える

まず2つのint型を持つ配列か判定し、適合したらwhenで合計が20超えるか再確認します

2. switch文 + when

void main() {
  int num = 15;
  switch(num) {
    case int n when n % 2 == 0:
      print("偶数");
    case int n when n % 2 != 0:
      print("奇数");
  }
}
Code language: PHP (php)

出力:奇数

先にint型か判定し、その後when内の条件を確認します。

3. switch式 + when

void main() {
  int num = 15;
  String res = switch(num) {
    int n when n % 2 == 0 => "偶数",
    _ => "奇数"
  };
  print(res);
}
Code language: PHP (php)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です