使用更具体的类型覆盖子类继承的属性

12

我尝试实现的简化示例如下:

public class Animal
{
    public virtual Teeth teeth {get;set;}
}

public class Mouse : Animal
{
    public override SmallTeeth teeth {get; set;} // SmallTeeth Inherits from Teeth
}
显然,这样做是不起作用的,因为在Mouse类中覆盖的牙齿必须与Animal类中的牙齿类型相同。但是是否可以实现类似于此的内容,在从Animal继承的任何函数中允许使用更多派生的类型?例如,如果Animal类包含一个咬的函数:
public void Bite()
{
    teeth.bite()
    Console.WriteLine("Ouch")
} 

我可以调用从Animal继承来的Bite()函数,它会使用Mouse类中类型为SmallTeeth的字段。这是可能的吗?如果不是,那么正确的方法是什么?


@gyttt1:如果我没记错的话,这与协变概念有关。你可以用Teeth覆盖通常的方式,你的实现仍然可以返回SmallTeeth类型的对象。 - Pramod Mangalore
2个回答

18

注意:本答案是在2016年编写的。自那时以来,C#已经(令人惊讶地!)添加了返回类型协变性。阅读本答案时请记住这一点。


您想要的功能称为返回类型协变性,而C#不支持它。(顺便说一下,C++支持。)

通常认为协变返回类型的理由是:

abstract class Animal
{
    public abstract Cage GetCage();
}
public class Fish : Animal
{
    public override Aquarium GetCage() { ... }
}

这并不合法,但如果它是合法的,那么它将是安全的。也就是说,如果你手上有一只动物,并要求一个笼子,即使得到的是一条鱼缸,你也会得到它。为什么?因为水族馆就是一种笼子。

然而您所提出的不仅是非法的,而且是不安全的:

Animal animal = new Mouse();
animal.Teeth = new TRexTeeth();

协定是setter可以接受任何类型的牙齿。通过使派生类在接受的内容更加严格,你违反了基类的协定。

所以不要这样做。

然而,在C#中有许多方法可以实现你想要的效果。

这里只是其中之一:

interface IAnimal
{
    Teeth Teeth { get; } // READ ONLY
}

class Mouse : IAnimal
{
    private SmallTeeth smallTeeth;
    public SmallTeeth Teeth 
    {
        get { return smallTeeth; }
    }

    Teeth IAnimal.Teeth { get { return this.Teeth; } }
}

现在,如果你将一个老鼠转换成IAnimal类型,你会得到返回Teeth属性的结果;而如果你正常使用老鼠,你会得到返回SmallTeeth属性的结果。

我在这里描述了另一种解决这个问题的方法:

C#是否支持返回类型协变?

另一篇答案中给出的通用解决方案也可以使用,不过个人更喜欢除非必要,否则不使用泛型。

搜索“C#中返回类型协变”以获取有关此模式的更多信息。


1
从语言学的角度来看,泛型的使用也是一件好事,但往往被忽视。由于名称选择不当导致的混淆很遗憾,对于第一次遇到新代码库的软件开发人员来说这是日常事件(我作为新成员和帮助新同事入职时都经历过这种情况)。如果我看到GetCage返回Aquarium在PR中,我会要求作者重新考虑命名。 Enclosure是我目前能想到的最普通的词。水族馆、笼子、罐子、小屋均可作为动物围栏,并且都可以作为GetEnclosure的有效返回类型。 - user1007074
1
C# 9 最近新增了协变返回:https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/ - wmeyer
@wmeyer:感谢您的提醒,我已经更新了答案。 - Eric Lippert

4
实际上是可以的。但是你不应该使用 generics 来包含类型约束,请参阅下面的评论和Eric Lippert的帖子,他详细解释了你想要或尝试在你的情况下实现的内容:
public class Animal<T>  where T : Teeth
{
    public virtual T teeth {get;set;}
}

public class Mouse : Animal<SmallTeeth>
{
    public override SmallTeeth teeth {get; set;} // SmallTeeth Inherits from Teeth
}

3
这很有趣,但我想在这里引入泛型可能会在其他地方造成复杂性/问题。 - Jonathon Reinhart
4
好的解决方案,但是它的缺点也应该被提到:动物们不再具有共同的超类(除了 object)。例如,你不能再创建一个 List<Animal> - Heinzi
2
是的@Heinzi,这是一个很好的答案,但不幸的是那个缺点已经成为了一项决定性因素! - Danny Herbert
1
你可以通过创建一个常规的Animal基类(因此Animal<T>:Animal)来解决缺乏共同基础的问题,并且你可以给Animal一个public Teeth UntypedTeeth属性,该属性返回与另一个属性相同的对象。然后,您可以根据需要使用通用类型或特定类型。 - Dave Cousineau
你可以让它们继承一个接口作为解决方法 - 类似于 public interface IAnimal ... class Animal<T> : IAnimal ... class Mouse : Animal<SmallTeeth> - Nick Bull

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接