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行给出错误&#34;无法将Implementation.InnerFoo
隐式转换为Base<string>.InnerFooBase<Base<string>.InnerBarBase<string>>
&#34;
我可以通过InnerFoo
派生InnerBarBase<string>
而不是InnerBar
来解决错误,但我不想要这样做。我需要代码来强制执行InnerFoo
和InnerBar
之间的关系。
InnerFoo
来自InnerFooBase<InnerBar>
,而InnerBar
来自InnerBarBase<string>
。如果GetInnerFoo
正在寻找InnerFooBase<InnerBarBase<string>>
,为什么不能自动将这些类型转换为其基数?
答案 0 :(得分:8)
为了说明失败的原因,我将按以下方式重命名您的课程:
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到鱼笼动物园的转换。
您只能进行此类转换 - 协变转换 - 如果类型为 interfaces ,则类型参数为引用类型,并且界面没有“添加”样式方法,变量参数标记为“out”。
回到您的域名:我们无法在需要InnerFoo
的上下文中使用InnerFooBase<InnerBarBase<string>>
。 InnerFoo
仅InnerFooBase
仅使用 InnerBar
,但我们需要InnerFooBase
可以使用任何 InnerBarBase<string>
。世界上可能存在InnerBarBase<string>
的派生类,而不是InnerBar
。所以类型系统拒绝让你这样做。
考虑到这是多么令人困惑,您可以考虑退后一步,询问您的泛型类型和嵌套类型的排列是否过于复杂,以至于未来的程序员无法理解。没有必要尝试捕获类型系统中程序行为的所有可能限制。正如我们刚才所见,当你尝试时,类型系统有时会强制执行你不打算表达的限制。
答案 1 :(得分:2)
如果泛型类实现了接口,那么它的所有实例 class可以转换为该接口。通用类是不变的。在 换句话说,如果输入参数指定
List<BaseClass>
,则为您 如果你试图提供一个,将会出现编译时错误List<DerivedClass>
。
在您的示例中,您希望返回类型为基类InnerBarBase<string>
,但您尝试返回派生类InnerFoo
。
答案 2 :(得分:0)
正如@Thiago所说,这是一个协方差和逆变问题。但是你可以通过使用InnerFooBase的TFoo类型参数的“out”规范来告诉编译器它是协变的。
即改变
protected abstract class InnerFooBase<TFoo> where TFoo : InnerBarBase<T>
到
protected interface IInnerFooBase<out TFoo> where TFoo : InnerBarBase<T>
这个问题对使用“out”规范有很好的解释: "out T" vs. "T" in Generics
请注意,InnerFooBase不再是一个抽象类,而是一个接口。这是使用“out”的限制。您可能想检查是否能够承担这样的设计变更。