Dart 泛型 - 类型不是子类型

Bre*_*ton 5 generics subtype dart

我从 Dart 通用函数中收到运行时错误:

 Widget card = widget.cardBuilder(item);
Run Code Online (Sandbox Code Playgroud)

产生:

 type '(Contact) => Widget' is not a subtype of type '(DeletableItem) => Widget'
Run Code Online (Sandbox Code Playgroud)

Contact 类定义为

class Contact
implements DeletableItem
{
Run Code Online (Sandbox Code Playgroud)

然后我有一个方法:

 class UndoableListView<T extends DeletableItem>
 {
      List<T> children;
      final Widget Function(T item) cardBuilder;
Run Code Online (Sandbox Code Playgroud)

这就是运行时错误发生的地方

 Widget buildItem(BuildContext context, int index) {
 T item = children[index];
 Widget card = widget.cardBuilder(item);   <<<<< error thrown here.
Run Code Online (Sandbox Code Playgroud)

我显然误解了泛型如何工作。

Contact 显然扩展了 DeleteableItem。

那么我做错了什么?

lrn*_*lrn 1

Contactextends DeletableItem,并且是它的子类型。

函数的参数是逆变的,所以

Widget Function(Contact)
Run Code Online (Sandbox Code Playgroud)

不是子类型

Widget Function(DeletableItem)
Run Code Online (Sandbox Code Playgroud)

事实上,情况恰恰相反。

函数之所以如此,是因为子类型意味着可替换性。Contact您可以在任何需要a 的地方使用a DeletableItem(因为它实现了整个DeletableItem接口), 的Contact子类型也是如此DeletableItem

您可以在任何需要Widget Function(DeletableItem)a 的地方使用 a ,因为您可以使用 any 调用该函数(并且它会按预期返回 a)。的一个子类型也是如此。Widget Function(Contact)ContactWidgetWidget Function(DeletableItem)Widget Function(Contact)

Dart 泛型是协变的,尽管它并不总是合理的。

这意味着 aUndoableListView<Contact>是 的子类型UndoableListView<DeletableItem>

The problem comes up because the cardBuilder field uses the class type parameter contravariantly in the function type,

      final Widget Function(T item) cardBuilder;
Run Code Online (Sandbox Code Playgroud)

So, when you do:

UndoableListView<DeletableItem> list = UndoableListView<Contact>();
var builder = list.cardBuilder;
Run Code Online (Sandbox Code Playgroud)

you get an error. The list.cardBuilder expression has a static type of Widget Function(DeletableItem), but a run-time type of Widget Function(Contact), which is not a subtype of the static type. That's a type soundness problem, one the compiler is aware of, and it inserts a type check to ensure that the builder variable won't end up with an invalidly typed value.

And that check throws.