Dart 列挙とシンボル

列挙型(enum)は、名前付きの定数値の集まりを定義するために使用され、コードをより読みやすくします。

この章では、Dart の列挙型の定義方法、拡張列挙型(メソッドとプロパティ付き)、および Symbol 型と Rune 型について紹介します。


enum 列挙型の定義

列挙型は、固定数の定数値を表すための特別なクラスです。

最も典型的な用途は、状態、方向、色などの有限の選択肢を表すことです。

実例

// 列挙型を定義
enum Status {
  pending,    // 未処理
  approved,   // 承認済み
  rejected,   // 拒否済み
  cancelled,  // キャンセル済み
}

void main() {
  // 列挙値を使用
  Status currentStatus = Status.pending;
  print('当前状态: $currentStatus');

  // .index を使用してインデックスを取得(0 から開始)
  print('索引值: ${currentStatus.index}');  // 0

  // .values を使用してすべての列挙値を取得
  print('所有状态: ${Status.values}');

  // すべての列挙値を反復処理
  for (var status in Status.values) {
    print('サンプル 状态: $status, 索引: ${status.index}');
  }

  // 文字列から列挙値を取得
  Status parsed = Status.values.byName('approved');
  print('解析された状態: $parsed');

  // switch で列挙型を使用
  switch (currentStatus) {
    case Status.pending:
      print('订单待处理');
      break;
    case Status.approved:
      print('订单已批准');
      break;
    case Status.rejected:
      print('注文が拒否されました');
      break;
    case Status.cancelled:
      print('订单已取消');
      break;
  }
}
当前状态: Status.pending
索引值: 0
所有状态: [Status.pending, Status.approved, Status.rejected, Status.cancelled]
サンプル 状态: Status.pending, 索引: 0
サンプル 状态: Status.approved, 索引: 1
サンプル 状态: Status.rejected, 索引: 2
サンプル 状态: Status.cancelled, 索引: 3
解析出的状态: Status.approved
订单待处理

列挙型と switch は最高の組み合わせです。switch ですべての列挙値を処理するとき、Dart コンパイラはすべてのケースをカバーしているかどうかをチェックします。これにより、分岐の漏れによるバグを防げます。


拡張列挙型(メソッドとプロパティ付き)

Dart 3.0 では拡張列挙型(Enhanced Enums)が導入され、列挙型がフィールド、メソッド、コンストラクタを持てるようになりました。

実例

// 拡張列挙型:メンバー変数、コンストラクタ、メソッドを持てる
enum HttpStatus {
  // 列挙値はコンストラクタを介してパラメータを渡す
  ok(200, '请求成功'),
  created(201, '资源已创建'),
  badRequest(400, '请求错误'),
  unauthorized(401, '未授权'),
  notFound(404, '资源未找到'),
  serverError(500, '服务器内部错误');

  // メンバー変数:各列挙値は code と message を持つ
  final int code;
  final String message;

  // コンストラクタ:const である必要がある
  const HttpStatus(this.code, this.message);

  // 列挙型メソッド
  bool get isSuccess => code >= 200 && code < 300;
  bool get isClientError => code >= 400 && code < 500;
  bool get isServerError => code >= 500;

  // 静的メソッド:ステータスコードから列挙値を検索
  static HttpStatus? fromCode(int code) {
    try {
      return HttpStatus.values.firstWhere((s) => s.code == code);
    } catch (_) {
      return null;
    }
  }
}

void main() {
  var status = HttpStatus.notFound;

  print('サンプル HTTP 状态: $status');
  print('状态码: ${status.code}');
  print('消息: ${status.message}');
  print('成功するかどうか: ${status.isSuccess}');
  print(クライアントエラーかどうか: ${status.isClientError});

  // 静的メソッドを使用して検索
  var found = HttpStatus.fromCode(201);
  if (found != null) {
    print('找到状态: ${found.message}');
  }

  var unknown = HttpStatus.fromCode(999);
  print('未知状态码: $unknown');
}
サンプル HTTP 状态: HttpStatus.notFound
状态码: 404
メッセージ: 资源未找到
是否成功: false
是否客户端错误: true
找到状态: 资源已创建
未知状态码: null

拡張列挙型により、列挙型は「名前付き定数の集合」から「振る舞いを持つ型」へと進化し、その列挙型に関連するロジックをカプセル化できます。

拡張列挙型のコンストラクタは const でなければならず、メンバー変数は final でなければなりません。これは、列挙値自体がコンパイル時の定数であるためです。


Symbol 型

Symbol は、Dart プログラム内の識別子(変数名、関数名など)を表します。

日常の開発ではあまり使われませんが、主にリフレクション(Reflection)やコード生成の場面で使用されます。

実例

void main() {
  // # プレフィックスを使用して Symbol を作成
  Symbol sym1 = #サンプル;
  Symbol sym2 = #helloWorld;

  print('Symbol 1: $sym1');  // Symbol("サンプル")
  print('Symbol 2: $sym2');  // Symbol("helloWorld")

  // Symbol の比較
  Symbol sym3 = #サンプル;
  print('sym1 == sym3: ${sym1 == sym3}');  // true

  // Symbol は Map のキーとして使用できる
  var metadata = {
    #author: 'サンプル',
    #version: '1.0.0',
    #description: 'Dart 教程',
  };
  print('作者: ${metadata[#author]}');
}
Symbol 1: Symbol("サンプル")
Symbol 2: Symbol("helloWorld")
sym1 == sym3: true
作者: サンプル

Symbol は Dart のコンパイル後、通常は圧縮(minified)されるため、Symbol("サンプル").toString() の結果を論理判断に頼ってはいけません。Symbol の正しい使い方は、dart:mirrors ライブラリを通じてリフレクション操作を行うことです。


Rune と Unicode

Rune は、Dart で Unicode コードポイントを表す型です。

Dart では、文字列は UTF-16 エンコードされたシーケンスです。

基本多言語面(BMP)以外の文字(絵文字など)を処理するには、Rune を使用する必要があります。

実例

void main() {
  // 文字列の Unicode コードポイントを取得
  String text = 'サンプル';
  print('字符: $text');
  print('码点列表: ${text.runes.toList()}');

  // 各文字とそのコードポイントを反復処理
  for (var char in text.runes) {
    print('${String.fromCharCode(char)} -> U+${char.toRadixString(16).toUpperCase().padLeft(4, '0')}');
  }

  // コードポイントから文字を作成
  var heart = String.fromCharCode(0x2764);
  print('心形: $heart');

  // 複数のコードポイントを持つ文字を処理(一部の絵文字など)
  var smile = '\u{1F600}';  // \u{} 構文を使用して BMP 外の文字を表す
  print('笑脸: $smile');

  // 複数のコードポイントを持つ文字のコードポイントを取得
  print('笑脸码点: ${smile.runes.map((r) => 'U+${r.toRadixString(16).toUpperCase()}').toList()}');
}
字符: サンプル
码点列表: [82, 85, 78, 79, 79, 66]
R -> U+0052
U -> U+0055
N -> U+004E
O -> U+004F
O -> U+004F
B -> U+0042
心形: &#x2764;
笑脸: &#x1f600;
笑脸码点: [U+1F600]

日常の開発のほとんどでは、Rune を直接操作する必要はなく、文字列自体が Unicode をうまく処理できます。

Rune は主に、文字コードポイントを正確に操作する必要がある場合に使用されます。たとえば、テキストエディタやフォントレンダリングなどの低レベルの場面です。

その他の拡張