为什么不能隐式地将一个类转换为它的泛型基类?

本文关键字:转换 基类 泛型 一个 不能 为什么 | 更新日期: 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来修复错误,但我想这样做。我需要代码来强制InnerFooInnerBar之间的关系。

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>>的上下文中使用InnerFooInnerFoo是只消耗 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"的一个限制。您可能需要检查您是否负担得起这样的设计更改。