为什么我不能隐式地将类转换为其通用基类?

时间:2016-10-06 23:58:48

标签: c#

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>
    {
    }
}

Fiddle

第18行给出错误&#34;无法将Implementation.InnerFoo隐式转换为Base<string>.InnerFooBase<Base<string>.InnerBarBase<string>>&#34;

我可以通过InnerFoo派生InnerBarBase<string>而不是InnerBar来解决错误,但我想要这样做。我需要代码来强制执行InnerFooInnerBar之间的关系。

InnerFoo来自InnerFooBase<InnerBar>,而InnerBar来自InnerBarBase<string>。如果GetInnerFoo正在寻找InnerFooBase<InnerBarBase<string>>,为什么不能自动将这些类型转换为其基数?

3 个答案:

答案 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>>InnerFooInnerFooBase仅使用 InnerBar,但我们需要InnerFooBase可以使用任何 InnerBarBase<string>。世界上可能存在InnerBarBase<string>的派生类,而不是InnerBar。所以类型系统拒绝让你这样做。

考虑到这是多么令人困惑,您可以考虑退后一步,询问您的泛型类型和嵌套类型的排列是否过于复杂,以至于未来的程序员无法理解。没有必要尝试捕获类型系统中程序行为的所有可能限制。正如我们刚才所见,当你尝试时,类型系统有时会强制执行你不打算表达的限制。

答案 1 :(得分:2)

看看Generic Classes

  

如果泛型类实现了接口,那么它的所有实例   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”的限制。您可能想检查是否能够承担这样的设计变更。