为什么不能隐式地将一个类转换为它的泛型基类?
本文关键字:转换 基类 泛型 一个 不能 为什么 | 更新日期: 2023-09-27 18:12:01
public abstract class Base<T>
{
protected abstract InnerFooBase<InnerBarBase<T>> GetInnerFoo();
protected abstract class InnerFooBase<TFoo> where TFoo : InnerBarBase<T>
{
}
protected abstract class InnerBarBase<TBar>
{
}
}
public class Implementation : Base<string>
{
protected override InnerFooBase<InnerBarBase<string>> GetInnerFoo()
{
return new InnerFoo(); // Error
}
class InnerFoo : InnerFooBase<InnerBar>
{
}
class InnerBar : InnerBarBase<string>
{
}
}
小提琴
第18行给出错误"Cannot implicit convert type Implementation.InnerFoo
to Base<string>.InnerFooBase<Base<string>.InnerBarBase<string>>
"
我可以通过让InnerFoo
来自InnerBarBase<string>
而不是InnerBar
来修复错误,但我不想这样做。我需要代码来强制InnerFoo
和InnerBar
之间的关系。
InnerFoo
来源于InnerFooBase<InnerBar>
, InnerBar
来源于InnerBarBase<string>
。如果GetInnerFoo
正在寻找InnerFooBase<InnerBarBase<string>>
,为什么不能自动将类型转换为它们的基?
为了说明失败的原因,我将重命名您的类,如下所示:
InnerBarBase<T> -> Cage<T>
string -> Fish
InnerBar -> GoldfishBowl
InnerFooBase -> Zoo
InnerFoo -> MiniAquarium
我将简化您的场景,使用赋值而不是虚拟重写。这个问题在赋值中更容易看到。
好吧。让我们设置类型。
class Animal {}
class Fish : Animal {}
class Cage<TAnimal> where TAnimal : Animal { }
笼子是用来装动物的。
class GoldfishBowl : Cage<Fish> { }
金鱼缸是一个可以装鱼的笼子。
class B<TAnimal> where TAnimal : Animal
{
public class Zoo<TCage> where TCage : Cage<TAnimal>
{
public void Add(TCage cage) {}
}
}
一个B<TAnimal>.Zoo<TCage>
是一个笼子的集合,可以容纳特定种类的动物。注意,我已经创建了一个"添加"。方法在原始程序中不存在。
class MiniAquarium : B<Fish>.Zoo<GoldfishBowl> { }
微型水族馆是一个只有金鱼缸的动物园。
现在的问题是:在我想要一个有鱼笼的动物园的情况下,我能使用一个迷你水族馆吗?不!原因:
B<Fish>.Zoo<Cage<Fish>> zoo = new MiniAquarium();
假设这是合法的。这是不合法的。但我们假设它是。出了什么问题?我创造了第二种鱼笼:
class SharkTank : Cage<Fish> { }
现在我完全可以合法地说:
zoo.Add(new SharkTank());
现在我们有一个迷你水族馆——根据定义,它只包含金鱼缸——里面有一个鲨鱼缸。
编译器唯一可能产生错误的地方是从MiniAquarium转换为zoo of fish cage。
你只能做这种类型的转换——协变转换——如果类型是接口,类型参数是引用类型,并且接口没有"样式方法,并将变化参数标记为"out"
回到你的领域:我们不能在需要InnerFooBase<InnerBarBase<string>>
的上下文中使用InnerFoo
。InnerFoo
是只消耗 InnerBar
s的InnerFooBase
,但我们需要一个可以消耗任何 InnerBarBase<string>
的InnerFooBase
。世界上可能存在InnerBarBase<string>
的派生类,而不是InnerBar
。因此,类型系统不允许您这样做。
考虑到这是多么令人困惑,您可能会考虑退一步,并询问您的泛型类型和嵌套类型的安排是否过于复杂,以至于未来的程序员无法理解。没有必要试图捕捉类型系统中对程序行为的所有可能限制。正如我们刚才看到的,当您尝试这样做时,有时类型系统会强制执行您不打算表达的限制。
看看泛型类。
如果泛型类实现了一个接口,则该接口的所有实例类可以强制转换为该接口。泛型类是不变的。在换句话说,如果一个输入参数指定了一个
List<BaseClass>
,那么您可以将得到编译时错误,如果您尝试提供List<DerivedClass>
.
在您的示例中,您期望返回类型为基类InnerBarBase<string>
,但您试图返回派生类InnerFoo
。
正如@Thiago提到的,这是一个协方差和逆变问题。但是你可以通过对InnerFooBase的TFoo类型参数使用"out"规范来绕过这个问题,告诉编译器它是协变的。
。, change
protected abstract class InnerFooBase<TFoo> where TFoo : InnerBarBase<T>
protected interface IInnerFooBase<out TFoo> where TFoo : InnerBarBase<T>
这个问题对于使用"out"规范有很好的解释:"T"与"T"在泛型
注意InnerFooBase不再是一个抽象类,而是一个接口。这是使用"out"的一个限制。您可能需要检查您是否负担得起这样的设计更改。