Dart: Dart 元编程与反射 — 注解、代码生成与反射机制

元编程是写代码的代码 — 自动化重复劳动,让开发者专注于业务逻辑。

1. 你将学到


2. 一个开发者的真实故事

(1) 痛点:手动序列化代码占 60% 开发量

Bob 的 DataPipeline 有 20 个数据模型类,每个都需要 fromJson/toJson 方法。手动编写 20 个类的序列化代码花了 3 天,而且经常出错 — 一次字段名拼错导致 100,000 条记录的解析失败。

(2) 代码生成的解法

Dart 选择代码生成而非反射,通过注解标注模型类,build_runner 自动生成序列化代码。

DART
import 'package:json_annotation/json_annotation.dart';
part 'order.g.dart';

@JsonSerializable()
class Order {
  final String id;
  final double amount;

  Order({required this.id, required this.amount});

  factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
  Map<String, dynamic> toJson() => _$OrderToJson(this);
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

(3) 收益


3. 注解

(1) 注解定义与使用

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:自定义注解

DART
// Define a custom annotation
class Column {
  final String name;
  final bool nullable;
  final String? defaultValue;

  const Column({
    required this.name,
    this.nullable = false,
    this.defaultValue,
  });
}

class Table {
  final String name;
  const Table(this.name);
}

// Apply annotations to a class
@Table('orders')
class Order {
  @Column(name: 'order_id')
  final String id;

  @Column(name: 'amount', nullable: false)
  final double amount;

  @Column(name: 'status', defaultValue: 'pending')
  final String status;

  Order({required this.id, required this.amount, this.status = 'pending'});
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:使用反射读取注解(VM only)

DART
import 'dart:mirrors';

// Read annotations using reflection (VM only!)
void printTableInfo(Type type) {
  final classMirror = reflectClass(type);

  // Read class-level annotations
  for (final metadata in classMirror.metadata) {
    if (metadata.reflectee is Table) {
      final table = metadata.reflectee as Table;
      print('Table: ${table.name}');
    }
  }

  // Read field-level annotations
  classMirror.declarations.forEach((key, declaration) {
    if (declaration is VariableMirror) {
      for (final metadata in declaration.metadata) {
        if (metadata.reflectee is Column) {
          final column = metadata.reflectee as Column;
          print('  ${declaration.simpleName}: ${column.name} '
              '(nullable: ${column.nullable}, default: ${column.defaultValue})');
        }
      }
    }
  });
}

void main() {
  printTableInfo(Order);
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
⚠️ 注意: dart:mirrors 只在 Dart VM 上可用。Flutter release 模式(AOT 编译)和 Web 不支持。


4. dart:mirrors 反射 API

(1) 反射能力概览

100%
graph TD
  A[Metaprogramming] --> B[dart:mirrors Reflection]
  A --> C[Annotations + Code Generation]
  B --> B1[Runtime flexible]
  B --> B2[VM only / AOT unavailable]
  C --> C1[Compile-time generation]
  C --> C2[AOT compatible / Flutter friendly]
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:反射 API 用法

DART
import 'dart:mirrors';

class Product {
  final String name;
  final double price;
  String category;

  Product({required this.name, required this.price, this.category = 'General'});

  String formatPrice() => '\$${price.toStringAsFixed(2)} USD';

  double applyDiscount(double rate) => price * (1 - rate);
}

void reflectOnProduct() {
  final mirror = reflectClass(Product);

  // List all instance methods
  print('Methods:');
  mirror.instanceMembers.forEach((name, member) {
    if (member is MethodMirror && !member.isConstructor && !member.isStatic) {
      print('  $name: ${member.returnType.reflectedType}');
    }
  });

  // Create instance via reflection
  final instance = mirror.newInstance(
    Symbol(''),
    [],
    {#name: 'Laptop', #price: 1299.99, #category: 'Electronics'},
  );

  // Invoke method via reflection
  final formatted = instance.invoke(#formatPrice, []);
  print('Formatted: ${formatted.reflectee}');

  final discounted = instance.invoke(#applyDiscount, [0.1]);
  print('Discounted: \$${discounted.reflectee.toStringAsFixed(2)} USD');
}

void main() {
  reflectOnProduct();
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
反射能力 API 说明
获取类信息 reflectClass(Type) 类名、方法、字段
创建实例 newInstance() 动态实例化
调用方法 invoke() 动态方法调用
读取字段 getField() 动态属性访问
读取注解 .metadata 获取元数据

5. 代码生成 vs 反射

(1) 对比与取舍

维度 dart:mirrors 反射 代码生成(build_runner)
运行时 灵活,运行时决策 编译时确定
AOT 兼容 不兼容 兼容
Flutter 支持 Release 模式不可用 全模式支持
Web 支持 不可用 支持
性能 运行时开销 零运行时开销
开发体验 无需生成步骤 需要 build_runner 步骤
调试 困难(动态分派) 简单(生成代码可读)
📌 重点: Dart 生态选择了"代码生成"而非"反射"。json_serializable、freezed、dart_mappable 等主流包都基于代码生成。Flutter 的 AOT 编译天然排斥反射。


6. 注解驱动的字段映射

(1) Bob 场景:DataPipeline 字段映射

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:手动注解处理器(模拟代码生成)

DART
// Custom annotations for field mapping
class FieldMapping {
  final String csvColumn;
  final String? defaultValue;
  final bool required;

  const FieldMapping({
    required this.csvColumn,
    this.defaultValue,
    this.required = true,
  });
}

class ModelMapping {
  final String tableName;
  const ModelMapping(this.tableName);
}

// Model with field mapping annotations
@ModelMapping('customers')
class Customer {
  @FieldMapping(csvColumn: 'customer_id')
  final String id;

  @FieldMapping(csvColumn: 'customer_name', defaultValue: 'Unknown')
  final String name;

  @FieldMapping(csvColumn: 'email', required: false)
  final String? email;

  @FieldMapping(csvColumn: 'total_spent', defaultValue: '0')
  final double totalSpent;

  Customer({
    required this.id,
    required this.name,
    this.email,
    this.totalSpent = 0,
  });

  // Manual mapping (in real project, this would be generated)
  static Customer fromCsvMap(Map<String, String> csvRow) {
    return Customer(
      id: csvRow['customer_id'] ?? '',
      name: csvRow['customer_name'] ?? 'Unknown',
      email: csvRow['email'],
      totalSpent: double.tryParse(csvRow['total_spent'] ?? '0') ?? 0,
    );
  }
}

void main() {
  final csvRow = {
    'customer_id': 'CUST-001',
    'customer_name': 'Alice',
    'email': 'alice@example.com',
    'total_spent': '52500.75',
  };

  final customer = Customer.fromCsvMap(csvRow);
  print('Customer: ${customer.id}, ${customer.name}');
  print('Email: ${customer.email ?? "N/A"}');
  print('Total: \$${customer.totalSpent.toStringAsFixed(2)} USD');
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

7. reflectionless 设计哲学

(1) 为什么 Dart 选择无反射

100%
graph TD
  A[Reflection vs Code Generation] --> B[Reflection]
  A --> C[Code Generation]
  B --> B1[Runtime flexibility]
  B --> B2[AOT incompatible]
  B --> B3[Flutter/Web blocked]
  B --> B4[Performance overhead]
  C --> C1[Compile-time certainty]
  C --> C2[AOT compatible]
  C --> C3[Flutter/Web friendly]
  C --> C4[Zero runtime cost]
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
设计原则 说明
AOT 优先 Flutter release 使用 AOT 编译,反射不可用
Tree shaking 编译器删除未使用代码,反射阻止 tree shaking
性能优先 反射有运行时开销,代码生成零开销
类型安全 代码生成保持类型安全,反射丧失类型检查

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:代码生成替代反射

DART
// Instead of reflection-based JSON parsing:
// dynamic parseJson(Map<String, dynamic> json, Type type) { ... }  // BAD

// Use code generation-based approach:
// 1. Define model with annotation
// @JsonSerializable()
// class Order { ... }

// 2. Run: dart run build_runner build

// 3. Generated code provides type-safe parsing:
// factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);

// Manual version (simulating generated code)
class Order {
  final String id;
  final double amount;

  Order({required this.id, required this.amount});

  // "Generated" fromJson
  factory Order.fromJson(Map<String, dynamic> json) => Order(
    id: json['id'] as String,
    amount: (json['amount'] as num).toDouble(),
  );

  // "Generated" toJson
  Map<String, dynamic> toJson() => {
    'id': id,
    'amount': amount,
  };
}

void main() {
  final json = {'id': 'ORD-001', 'amount': 1500.0};
  final order = Order.fromJson(json);  // Type-safe!
  print(order.toJson());
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

8. 完整示例:DataPipeline 注解驱动映射

DART
// ============================================
// DataPipeline Annotation-Driven Mapping
// Simulates code generation for CSV-to-Model
// ============================================

// Annotations
class CsvField {
  final String column;
  final String? defaultValue;
  final bool required;

  const CsvField({
    required this.column,
    this.defaultValue,
    this.required = true,
  });
}

class CsvModel {
  final String fileName;
  const CsvModel(this.fileName);
}

// Model classes
@CsvModel('orders')
class Order {
  @CsvField(column: 'order_id')
  final String id;

  @CsvField(column: 'total_amount', defaultValue: '0')
  final double amount;

  @CsvField(column: 'order_status', defaultValue: 'pending')
  final String status;

  @CsvField(column: 'category', required: false)
  final String? category;

  Order({
    required this.id,
    required this.amount,
    this.status = 'pending',
    this.category,
  });

  // In real project, this would be generated by build_runner
  static Order fromCsvRow(Map<String, String> row) => Order(
    id: row['order_id'] ?? '',
    amount: double.tryParse(row['total_amount'] ?? '0') ?? 0,
    status: row['order_status'] ?? 'pending',
    category: row['category'],
  );

  double get tax => amount * 0.08;
  double get total => amount + tax;

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status${category != null ? ", $category" : ""})';
}

// Mapper (simulating generated code)
class CsvMapper``<T>`` {
  final T Function(Map<String, String>) fromCsvRow;

  CsvMapper(this.fromCsvRow);

  List``<T>`` mapAll(List<Map<String, String>> rows) =>
      rows.map(fromCsvRow).toList();

  (List``<T>`` valid, List<(int, String)> errors) mapSafe(
      List<Map<String, String>> rows) {
    final valid = ``<T>``[];
    final errors = <(int, String)>[];

    for (var i = 0; i < rows.length; i++) {
      try {
        valid.add(fromCsvRow(rows[i]));
      } catch (e) {
        errors.add((i + 1, e.toString()));
      }
    }
    return (valid, errors);
  }
}

void main() {
  // Simulated CSV data (already parsed into maps)
  final csvRows = <Map<String, String>>[
    {'order_id': 'ORD-001', 'total_amount': '1500.00', 'order_status': 'completed', 'category': 'Electronics'},
    {'order_id': 'ORD-002', 'total_amount': '3200.50', 'order_status': 'completed', 'category': 'Electronics'},
    {'order_id': 'ORD-003', 'total_amount': '890.00', 'order_status': 'pending', 'category': 'Clothing'},
    {'order_id': 'ORD-004', 'total_amount': '50.00', 'order_status': 'completed'},
  ];

  // Map to model objects
  final mapper = CsvMapper``<Order>``(Order.fromCsvRow);
  final (valid, errors) = mapper.mapSafe(csvRows);

  print('=== DataPipeline CSV Import Report ===');
  print('Rows:   ${csvRows.length}');
  print('Valid:  ${valid.length}');
  print('Errors: ${errors.length}');

  if (errors.isNotEmpty) {
    print('\nErrors:');
    for (final (line, msg) in errors) {
      print('  Line $line: $msg');
    }
  }

  // Process valid orders
  double totalRevenue = 0;
  for (final order in valid) {
    if (order.status == 'completed') {
      totalRevenue += order.total;
    }
    print('  $order');
  }

  print('\nRevenue (completed): \$${totalRevenue.toStringAsFixed(2)} USD');
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

输出:

TEXT 📖 仅展示
=== DataPipeline CSV Import Report ===
Rows:   4
Valid:  4
Errors: 0
  Order(ORD-001, $1500.00, completed, Electronics)
  Order(ORD-002, $3200.50, completed, Electronics)
  Order(ORD-003, $890.00, pending, Clothing)
  Order(ORD-004, $50.00, completed)

Revenue (completed): $5132.54 USD

❓ 常见问题

Q:为什么 Flutter 不支持 dart:mirrors? A:Flutter 使用 AOT 编译为原生代码,AOT 需要在编译时确定所有类型信息。反射在运行时动态查找类型,与 AOT 的 tree shaking 和编译优化冲突。

Q:注解本身有什么用? A:注解本身不执行任何逻辑,只是元数据。它们需要被反射读取(VM)或代码生成器处理(build_runner)才能发挥作用。

Q:build_runner 每次修改代码都要重新运行吗? A:是的,但有 --watch 模式可以监听文件变化自动重新生成。开发时用 watch 模式,发布前用 build 模式。

Q:json_serializable 和手动写 fromJson 有什么区别? A:json_serializable 自动生成代码,避免手写错误,支持嵌套对象、自定义转换。手写简单但容易出错,嵌套对象更痛苦。

Q:Dart 将来会支持宏(Macros)吗? A:Dart 团队正在开发宏系统(macro package),旨在替代 build_runner 的部分功能,提供更好的开发体验。但目前尚未稳定。

Q:代码生成会增加包体积吗? A:会,因为生成的代码会被包含在编译产物中。但反射也会增加体积(阻止 tree shaking),两者差距不大。

Q:如何调试生成的代码? A:生成的 .g.dart 文件可以直接打开阅读和调试。build_runner 生成的代码是标准 Dart 代码,可以设断点。


📖 小节


📝 作业

  1. 基础题(难度⭐):定义 3 个自定义注解(@ApiEndpoint、@Required、@DefaultValue),应用到一个类上。用 dart:mirrors 读取注解信息(注意只能在 VM 运行)。
  2. 进阶题(难度⭐⭐):创建一个 json_serializable 项目,为一个包含 5 个字段的 Order 类生成 fromJson/toJson 代码。查看生成的 .g.dart 文件,理解生成逻辑。
  3. 挑战题(难度⭐⭐⭐):设计一个简单的代码生成器:读取带有 @CsvField 注解的类定义,生成 fromCsvRow 静态方法。提示:可以用 source_gen 包或简单的字符串模板。

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏