英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论