Dart运行时类型错误:'(int) => void' 非 '(Object) => void' 子类型
先给你拆解核心原因:这是Dart的函数参数逆变性和泛型类默认不变性共同导致的,跟你想的“子类就能兼容”不是一回事。
核心问题1:函数参数是逆变的,不是协变的
你以为(int)=>void能当(Object)=>void用?恰恰相反。Dart里函数参数的类型兼容性是逆变的——如果Sub是Super的子类,那Function(Super)是Function(Sub)的子类型,反过来不行。
放到你的代码里:
- 你创建的
DropdownField<int>里,setValue是(int)=>void,只能接受int类型的参数; - 但你把它当作
DropdownField<Object>处理时,程序期望的setValue是(Object)=>void,得能接受任何Object子类(比如String、bool)。
如果允许把(int)=>void赋值给(Object)=>void,那万一有人传个String进去,你的函数直接就崩了。所以Dart在运行时会拦截这种不安全的类型转换,抛出你看到的错误。
核心问题2:泛型类默认是不变的
DropdownField<V>这种泛型类,默认是不变的——哪怕int是Object的子类,DropdownField<int>也不是DropdownField<Object>的子类型。
你代码里的if (field is DropdownField<Object>)理论上不该匹配DropdownField<int>实例,但大概率你实际写的时候是省略了泛型参数(比如写成field is DropdownField),Dart会允许你把它强转成DropdownField<Object>,这就埋下了不安全的隐患,直接导致后续调用_buildDropdownField时触发类型错误。
修复方案
方案1:给泛型参数加协变标记
修改DropdownField的泛型参数为covariant V,这样DropdownField<int>就能被当作DropdownField<Object>处理:
class DropdownField<covariant V> extends ModelField { final void Function(V value) setValue; final List<V> options; DropdownField({ required this.setValue, required this.options, }); }
⚠️ 注意:协变会降低类型安全性,要是后续有人给setValue传个非int的Object类型参数,还是会崩,所以只在你能确保不会出现这种情况时用。
方案2:用泛型类型变量安全处理
在_renderField里通过泛型类型变量捕获实际类型,避免不安全的强转:
void _renderField(ModelField field) { if (field is DropdownField) { _buildDropdownField(field as DropdownField<dynamic>); } }
用dynamic绕开编译时检查,但会丢失部分类型安全,适合快速修复。
方案3:Dart3.0+用模式匹配
如果你的项目用Dart3.0及以上,可以用模式匹配直接处理,不用显式转型:
void _renderField(ModelField field) { if (field case DropdownField(:final options)) { if (options.isNotEmpty) { field.setValue(options.first); } } }
让Dart自动处理类型推断,既安全又简洁。
总结
你碰到的问题不是继承关系的问题,是Dart为了类型安全设计的函数逆变和泛型不变性导致的。选哪种修复方式,看你对类型安全性的要求和业务场景。
内容的提问来源于stack exchange,提问作者TSR

