英文:
dart enforce subtype in generic type (disallow base type) VS switch(Foo<T>)
问题
以下是您要翻译的代码部分:
sealed class Base { /* ... */ }
class X extends Base { /* ... */ }
class Y extends Base { /* ... */ }
class Foo<T extends Base> {
   final T t;
   const Foo({required this.t});
}
阅读文档似乎不可能限制Foo<T>实例的类型,以便T必须是Base的真正子类型,不能是Base。这是否可能?
背景是我想要在Foo<T>实例上进行切换:
final a = Foo<X>(/* ... */);
final result = switch (a) {
  Foo<X> _ => 'x',
  Foo<Y> _ => 'y',
  Foo<Base> _ => throw 'does not exist'; // <--
}
missing_enum_constant_in_switch规则要求我指定Base _情况。这很糟糕,因为它匹配所有情况,所以如果我们以后添加一个新的class Z extends Base {/* ... */},该规则将不再报告缺少情况,而只是使用Foo<Base>情况并抛出does not exist。
考虑到Base是sealed,因此它也是abstract的,因此不能被实例化,因此我希望编译器不要求这种情况 - 但我理解它确实需要,因为一般来说类型限制允许Foo<Base>。
作为一种解决方法,我目前正在切换a.t。
final a = Foo<X>(/* ... */);
final result = switch (a.t) {
  X _ => 'x',
  Y _ => 'y',
  // 不需要`Base`情况。
}
这在示例中看起来不错,但在实际生产代码中非常繁琐且容易出错,因为如果下游函数依赖于a而不是a.t,则需要手动进行未检查的强制类型转换:
final a = Foo<X>(/* ... */);
final result = switch (a.t) {
  // 这是不可能的
  X _ => somethingX(a as Foo<X>),
  Y _ => somethingY(a as Foo<Y>),
}
这不会起作用,因为a的类型是Foo<Base>,因此Flutter会抛出type 'Foo<Base>' is not a subtype of type 'Foo<X>' in type cast。
因此,我们必须进一步解决问题,通过像这样更改somethingX和somethingY以接受下转换的值:
final a = Foo<X>(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ => somethingX(a, t),
  Y _ => somethingY(a, t),
}
或者通过执行类似以下操作:
final a = Foo<X>(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ => somethingX(a.cast<X>()),
  Y _ => somethingY(a.cast<Y>()),
}
其中cast是:
Foo<T extends Base> {
  // ...
  Foo<T2> cast<T2 extends Base>() => Foo(
    t: t as T2,
  );
}
英文:
Given such classes
sealed class Base { /* ... */ }
class X extends Base { /* ... */ }
class Y extends Base { /* ... */ }
class Foo<T extends Base> {
   final T t;
   const Foo({required this.t});
}
Reading the docs it seems impossible to restrict the type of an instance of Foo<T> so that T must be a real sub types of Base and may not be Base. Is it possible?
Background is that I want to switch over a Foo<T> instance:
final a = Foo<X>(/* ... */);
final result = switch (a) {
  Foo<X> _ => 'x',
  Foo<Y> _ => 'y',
  Foo<Base> _ => throw 'does not exist'; // <--
}
The missing_enum_constant_in_switch rule requires me to specify the Base _ case. That is bad, because it matches all cases, so that if we later add a new class Z extends Base {/* ... */}, the rule would no longer report a missing case but just use the Foo<Base> case and throw does not exist.
Given that Base is sealed it also is abstract and hence cannot be instantiated and hence I would expect the compiler to not require that case - but I understand that it does, because in general the type restriction allows for Foo<Base>.
As a workaround I am currently switching over a.t.
final a = Foo<X>(/* ... */);
final result = switch (a.t) {
  X _ => 'x',
  Y _ => 'y',
  // no `Base` case is required.
}
This looks fine in an example, but is very cumbersome and error prone in realistic production code, because it would require a manual unchecked cast of a if downstream functions depend on a rather than on a.t:
final a = Foo<X>(/* ... */);
final result = switch (a.t) {
  // THIS IS NOT POSSIBLE
  X _ => somethingX(a as Foo<X>),
  Y _ => somethingY(a as Foo<Y>),
}
This would not work, because a is of type Foo<Base> and hence flutter throws type 'Foo<Base>' is not a subtype of type 'Foo<X>' in type cast.
Hence, we further must workaround by changing somethingX and somethingY to also accept the downcasted value like this:
final a = Foo<X>(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ => somethingX(a, t),
  Y _ => somethingY(a, t),
}
or by doing something like:
final a = Foo<X>(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ => somethingX(a.cast<X>()),
  Y _ => somethingY(a.cast<Y>()),
}
where cast is:
Foo<T extends Base> {
  // ...
  Foo<T2> cast<T2 extends Base>() => Foo(
    t: t as T2,
  );
}
答案1
得分: 1
这是不可能的。
Dart类型参数仅受子类型检查的限制,因此没有办法同时接受X和Y,并且不接受Base,根据当前的定义是无法做到的。
您可能会尝试的一个方法,实际上是行不通的,那就是有一个隐藏的共享超类:
class Base {}
class _SecretBase extends Base {}
class X extends _SecretBase {}
class Y extends _SecretBase {}
class Foo<T extends _SecereteBase> { ... }
然后您可能会认为,在您的库之外的某个地方,不会能够创建Foo<_SecretBase>,并且他们不能使用Base,因此必须使用Base的适当子类型之一。
不幸的是,这不起作用,因为像new Foo()这样的原始类型将会"实例化到边界"并创建一个Foo<_SecretBase>,即使调用者无法编写类型。
因此,无法将类型参数限制为共享超类型的适当子类型。
英文:
It's not possible.
Dart type parameters are only restricted by subtype checks, so there is no way to accept both X and Y and also not accept Base, not with the current definitions.
The one hack you'd probably try, and which won't actually work, is to have a hidden shared superclass:
class Base {}
class _SecretBase extends Base {}
class X extends _SecretBase {}
class Y extends _SecretBase {}
class Foo<T extends _SecereteBase> { ... }
Then you'd think that someone outside of your library would not be able to create a Foo<_SecretBase>, and they can't use Base, so would have to use one of the proper subtypes of Base.
Sadly it doesn't work, since a raw type like new Foo() would "instantiate to bounds" and create a Foo<_SecretBase>, even if the caller couldn't write the type.
So, it's not possible to restrict type arguments to proper subtypes of a shared supertype.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论