在飞镖中向下转换 Future 的泛型

Downcasting generic of Future in dart

我有一个具有通用参数的未来,它是另一个 class (B extends A) 的超级 class (A)。我知道 Future 值的实例属于子类型。为什么我不能在 dart 中将 Future<A> 向下转换为 Future<B>?如果我打开 Future 一次,然后使用 async/await 再次包装它,它就可以工作。

这是一个例子:

class A {}
class B extends A{}

void main() {
  Future<A> getFuture() async { return B();}
  Future<B> getBasA() { return getFuture() as Future<B>;}
  Future<B> getBasAasync() async { return (await getFuture()) as B;}

  print(getBasAasync()); // Works
  print(getBasA()); // Throws at runtime
}

为了好奇和作为问题的动机,这里有一个更贴近现实的例子。我有一个发出数据包的流,我对其进行过滤,然后像这样获得第一个:

Future<T> getResponse<T extends ReceivedPacket>() =>
  getStream<ReceivedPacket>().firstWhere((packet) => packet is T) as Future<T>; //throws

Future<T> getResponse<T extends ReceivedPacket>() async { //works
  return (await  getStream<ReceivedPacket>().firstWhere((packet) => packet is T)) as T;
} 

PS:我已经在 Typescript (will happily compile and run) and C# 中尝试过了(不会编译,但我的 C# 知识非常有限)。我知道这个问题的答案可能是“因为飞镖类型系统就是这样工作的”。我只是很困惑,因为我原以为它要么像 C# 那样在编译时失败,要么像打字稿一样在运行时工作。

您将 getFuture() 声明为 returning Future<A> 但使用了 async 关键字,因此 Dart 会自动将 return B(); 转换为(本质上) return Future<A>.value(B());. returned Future 从来都不是 Future<B>,因此不能向下转换为它。

显式创建 Future 会如您所愿:

Future<A> getFuture() { return Future<B>.value(B()); }

你可能会争辩说 Dart 在 async 函数中转换 return x; 时,它应该创建一个 Future<T>,其中 Tx 的静态类型而不是从函数的 return 类型推断它。正如 lrn 在评论中解释的那样,这是不可能的,因为你可能有:

class C extends A {}

Future<A> getFuture() async {
  await Future.delayed(const Duration(seconds: 1));

  if (Random().nextBool()) {
    return B();
  } else {
    return C();
  }
}

调用者必须立即得到一个Future,但直到Future最终才知道它的值是B还是C完成。

I'd have expected it either to fail at compile time like C#

我对 C# 的经验也非常有限,但我认为 C# 会给你一个编译错误,因为 C# 不认为 Generic<SubType>Generic<SuperType> 的子类型,而 Dart 会。