【问题标题】:Generic overridden method with subclass as return type以子类作为返回类型的通用重写方法
【发布时间】:2020-12-21 09:17:47
【问题描述】:

假设我有一个基类 Animal。

public abstract class Animal;

这个动物类有抽象方法:

public abstract T Copy<T>() where T : Animal

当此方法在 Lion 类中被覆盖时:

public class Lion : Animal
{
    string roar = "Roar";
}

我希望返回没有参考资料的这只狮子的副本。所以我认为应该像这样覆盖它:

public abstract T Copy<T>()
{
    return new Lion(){ 
        roar = this.roar;
    }
}

但这是不允许的,因为 Lion 无法转换为 T 类型(即动物)。

这是一个可以理解的错误,因为并非每只动物都一定是狮子。

但是我如何设置这种情况,复制方法知道它应该返回子类,在这种情况下是 Lion,并且不必将各种类型转换为正确的类型?并且仍然让每个动物都有一个方法 Copy()。

【问题讨论】:

  • 为什么你认为强制所有子类都有这样的Copy 方法很有用?您可以在每个子类中声明一个非泛型 Copy

标签: c# generics inheritance


【解决方案1】:

在这种情况下你不需要使用泛型方法,因为每个Lion 都是一个Animal,所以你可以简单地让Copy 返回一个Animal

public abstract class Animal
{
    public abstract Animal Copy();
}

public class Lion : Animal
{
    public string Roar {get; set;} = "Roar";
    
    public override Animal Copy()
    {
        return new Lion
        {
            Roar = this.Roar
        };
    }
}

然后像这样测试它:

public static void Main()
{
    var lion1 = new Lion();
    var lion2 = lion1.Copy();
    
    Console.WriteLine(lion1 == lion2);
}

按预期将false 打印到控制台,因为C# 类是引用类型,默认的== 实现会检查类上的引用等于,并且我们的两个狮子不是内存中的同一个对象。

现在Animal 的每个子类都必须实现返回Animal 的方法Copy。哪种动物无所谓

【讨论】:

  • 这是一个很好的解决方案,但是当我想使用 Roar 变量时,我仍然必须强制转换为 Lion 类型,我想尽可能少地这样做。如果这是必要的并且无法更改,那就这样吧。但它仍然会让我感到困扰,除了使用演员表之外没有其他方法可以做到这一点
  • 这听起来像是架构错误。如果您需要直接访问从抽象类继承的类上的值,则不需要抽象类,或者该属性应该位于基本抽象类上。在这种情况下,类似于Sound,因为(几乎)每只动物都会发出某种声音。否则,我建议您按照 Lasse V. Karlsen 的建议进行,因为这不过是您现在的尝试
  • 我希望能够访问继承类的值是怎样的架构错误?而不是“声音”,假设我想访问狮子的“鬃毛”,那么狮子仍然从动​​物继承并继承“声音”是合乎逻辑的。但是我不应该仍然可以访问狮子的“鬃毛”吗?
  • 如果您需要访问 Mane,那么您首先不应该将您的 Lion 投射到动物身上。这让我们回到了您通过使Copy 通用化来实现的目标,而解决方案就是 Lasse V. Karlsen 在他们的回答中提出的建议
【解决方案2】:

在这种情况下你不能使用泛型,它们不支持你想做的事情。

但是,如果您使用的是 C# 9,则可以使用新的“协变返回类型”支持。

具体来说,您可以这样编写代码:

public abstract class Animal
{
    public abstract Animal Copy();
}

public class Lion : Animal
{
    public override Lion Copy() => new Lion();
    //               ^
    //  notice that I used Lion here, not Animal
}

如果您通过声明为Animal 类型的引用调用Copy,但引用了Lion 的实例,您将返回一个类型为Animal 的值,其中包含Lion 引用。

但是,如果您通过类型为 Lion 的引用调用 Copy,您将返回一个类型为 Lion 的引用。

例子:

Animal a = new Lion();
Animal c = a.Copy(); <-- holding a Lion, but type is Animal

Lion l = new Lion();
Lion l2 = l.Copy();  <-- type is now Lion as well

如果您不在 C# 9 上,另一种选择是让 Copy 方法知道派生类的类型,方法是使 Animal 成为泛型:

void Main()
{
    Animal<Lion> a = new Lion();
    Lion c1 = a.Copy(); // <-- notice that this is now typed to Lion as well
    
    Lion l = new Lion();
    Lion c2 = l.Copy();
}

public abstract class Animal<T> where T : Animal<T>
{
    public abstract T Copy();
}

public class Lion : Animal<Lion>
{
    public override Lion Copy() => new Lion();
}

然而,这意味着您必须始终将 Animal 声明为正确的动物类型,因此您失去了拥有此基类的一些好处。

【讨论】:

  • 不幸的是我无法升级到 C# 9,而第二种解决方案对我来说似乎有点过分,仅针对单一方法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-22
  • 1970-01-01
  • 2011-11-09
  • 2012-06-10
  • 2022-10-24
相关资源
最近更新 更多