You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带泛型的Dart typedef引发子类型错误问题求助

问题描述

运行代码时抛出类型不匹配错误:

Unhandled exception:
type '(OnBadAir) => bool' is not a subtype of type '(Event) => bool'
#0 Fork._dispatch
typedef.dart:33
#1 main

这段代码提取自fsm2包,由于事件队列需处理不同类型的事件,无法做强类型标注,因此遇到了typedef逆变导致的类型问题。需解决如何向事件队列传递可被调度器处理的condition。

完整复现代码:

import 'dart:async';

class Event {}

class OnBadAir extends Event {
  OnBadAir(this.quality);
  int quality;
}

typedef GuardCondition<E extends Event> = bool Function(E event);

class _QueuedEvent {
  _QueuedEvent(this.event);
  Event event;
}

class Fork<E extends Event> {
  Fork(this.td);

  TransitionDefinition td;
  final _eventQueue = <_QueuedEvent>[];

  void queue(E event) {
    _eventQueue.add(_QueuedEvent(event));
  }

  /// dequeue the next event and transition it.
  Future<void> _dispatch() async {
    assert(_eventQueue.isNotEmpty, 'The event queue is in an invalid state');
    final event = _eventQueue.first;

    /// crashes here.
    if (td.condition(event.event)) {
      print('it worked');
    }
  }
}

class TransitionDefinition<E extends Event> {
  TransitionDefinition({required this.condition});
  final GuardCondition<E> condition;
}

void main() async {
  final fork = Fork<OnBadAir>(
      TransitionDefinition<OnBadAir>(condition: (e) => e.quality < 10))
    ..queue(OnBadAir(10));

  await fork._dispatch();
}

问题分析

错误根源在于:

  1. Fork类中的td成员未指定泛型,被隐式推断为TransitionDefinition<Event>,但实际传入的是TransitionDefinition<OnBadAir>。
  2. Dart中函数类型是逆变的:bool Function(OnBadAir)无法赋值给bool Function(Event)——后者要求能接收任意Event类型参数,而前者只能处理OnBadAir子类。

解决方案

方案1:对齐泛型约束(单一事件类型场景)

修改Fork类,让td的泛型与Fork的泛型E保持一致,并安全转换队列中的事件类型:

class Fork<E extends Event> {
  Fork(this.td);

  // 指定td的泛型为E,与Fork泛型对齐
  TransitionDefinition<E> td;
  final _eventQueue = <_QueuedEvent>[];

  void queue(E event) {
    _eventQueue.add(_QueuedEvent(event));
  }

  Future<void> _dispatch() async {
    assert(_eventQueue.isNotEmpty, 'The event queue is in an invalid state');
    final queuedEvent = _eventQueue.first;
    // 队列中的事件都是通过queue传入的E类型,转换安全
    final event = queuedEvent.event as E;

    if (td.condition(event)) {
      print('it worked');
    }
  }
}

此方案类型安全性高,适合Fork实例仅处理单一事件类型的场景。

方案2:调整条件函数的类型适配(多事件类型场景)

如果队列需要兼容多种事件类型,可修改GuardCondition的定义,让函数先判断事件类型再处理:

// 修改typedef,参数统一为Event类型
typedef GuardCondition<E extends Event> = bool Function(Event event);

class TransitionDefinition<E extends Event> {
  TransitionDefinition({required this.condition});
  final GuardCondition<E> condition;
}

// 使用时在条件函数内先做类型判断
void main() async {
  final fork = Fork<OnBadAir>(
      TransitionDefinition<OnBadAir>(condition: (event) {
    if (event is OnBadAir) {
      return event.quality < 10;
    }
    return false;
  }))
    ..queue(OnBadAir(10));

  await fork._dispatch();
}

此方案灵活性更高,适合队列中存在多种Event子类的场景。


内容的提问来源于stack exchange,提问作者Brett Sutton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 10:17:39