C#:覆盖返回类型

Svi*_*ish 73 c# inheritance types overriding covariance

有没有办法覆盖C#中的返回类型?如果是这样,如果不是为什么以及推荐的做法是什么?

我的情况是我有一个抽象基类的接口及其后代.我想这样做(确实不是,但作为一个例子!):

public interface Animal
{
   Poo Excrement { get; }
}

public class AnimalBase
{
   public virtual Poo Excrement { get { return new Poo(); } }
}

public class Dog
{
  // No override, just return normal poo like normal animal
}

public class Cat
{
  public override RadioactivePoo Excrement { get { return new RadioActivePoo(); } }
}
Run Code Online (Sandbox Code Playgroud)

RadioactivePoo当然继承自Poo.

我想要这样做的原因是,那些使用Cat对象的人可以使用该Excrement属性而不必Poo投入RadioactivePoo,例如,Cat仍然可能是Animal列表的一部分,用户可能不一定知道或关心他们的放射性便便.希望有意义......

据我所知,编译器至少不允许这样做.所以我想这是不可能的.但是你会推荐什么作为解决方案呢?

Pao*_*sco 47

通用基类怎么样?

public class Poo { }
public class RadioactivePoo : Poo { }

public class BaseAnimal<PooType> 
    where PooType : Poo, new() {
    PooType Excrement {
        get { return new PooType(); }
    }
}

public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }
Run Code Online (Sandbox Code Playgroud)

编辑:一种新的解决方案,使用扩展方法和标记界面......

public class Poo { }
public class RadioactivePoo : Poo { }

// just a marker interface, to get the poo type
public interface IPooProvider<PooType> { }

// Extension method to get the correct type of excrement
public static class IPooProviderExtension {
    public static PooType StronglyTypedExcrement<PooType>(
        this IPooProvider<PooType> iPooProvider) 
        where PooType : Poo {
        BaseAnimal animal = iPooProvider as BaseAnimal;
        if (null == animal) {
            throw new InvalidArgumentException("iPooProvider must be a BaseAnimal.");
        }
        return (PooType)animal.Excrement;
    }
}

public class BaseAnimal {
    public virtual Poo Excrement {
        get { return new Poo(); }
    }
}

public class Dog : BaseAnimal, IPooProvider<Poo> { }

public class Cat : BaseAnimal, IPooProvider<RadioactivePoo> {
    public override Poo Excrement {
        get { return new RadioactivePoo(); }
    }
}

class Program { 
    static void Main(string[] args) {
        Dog dog = new Dog();
        Poo dogPoo = dog.Excrement;

        Cat cat = new Cat();
        RadioactivePoo catPoo = cat.StronglyTypedExcrement();
    }
}
Run Code Online (Sandbox Code Playgroud)

这样Dog和Cat都继承自Animal(如评论中所述,我的第一个解决方案没有保留继承).
有必要使用标记接口明确标记类,这很痛苦,但也许这可以给你一些想法......

第二次编辑 @Svish:我修改了代码以明确地表明扩展方法没有以任何方式强制执行iPooProvider继承的事实BaseAnimal."更强烈的打字"是什么意思?

  • 你不要放弃狗和猫之间的pollymorphism吗? (8认同)

Dan*_*nas 31

这称为返回类型协方差,尽管有些人的意愿,但一般不支持C#或.NET .

我要做的是保持相同的签名,但ENSURE在派生类中添加一个附加子句,我确保这个子句返回一个RadioActivePoo.所以,简而言之,我是通过合同设计做的,我不能通过语法做.

其他人喜欢伪造它.我猜,没关系,但我倾向于节省"基础设施"代码行.如果代码的语义足够清楚,我很高兴,通过契约设计让我实现了这一点,尽管它不是编译时机制.

对于泛型也是如此,其他答案表明.我会使用它们的原因不仅仅是返回放射性便便 - 但这只是我.

  • "我会使用它们的原因不仅仅是返回放射性的便便 - 但那只是我"属于我最喜欢的自己的名单:) (5认同)

rob*_*rob 19

我知道这个问题已经有很多解决方案,但我想我已经找到了解决现有解决方案问题的解决方案.

由于以下原因,我对一些现有解决方案不满意:

  • 保罗·特德斯科的第一个解决方案:猫与狗没有共同的基础.
  • Paolo Tedesco的第二个解决方案:它有点复杂,难以阅读.
  • Daniel Daranas的解决方案:这样可行,但它会使用大量不必要的转换和Debug.Assert()语句来混乱代码.
  • hjb417的解决方案: 此解决方案不允许您将逻辑保留在基类中.在这个例子中,逻辑是非常简单的(调用构造函数),但在现实世界的例子中它不会.

我的解决方案

这个解决方案应该通过使用泛型和方法隐藏来克服我上面提到的所有问题.

public class Poo { }
public class RadioactivePoo : Poo { }

interface IAnimal
{
    Poo Excrement { get; }
}

public class BaseAnimal<PooType> : IAnimal
    where PooType : Poo, new()
{
    Poo IAnimal.Excrement { get { return (Poo)this.Excrement; } }

    public PooType Excrement
    {
        get { return new PooType(); }
    }
}

public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }
Run Code Online (Sandbox Code Playgroud)

使用此解决方案,您无需覆盖Dog OR Cat中的任何内容!多么酷啊?以下是一些示例用法:

Cat bruce = new Cat();
IAnimal bruceAsAnimal = bruce as IAnimal;
Console.WriteLine(bruce.Excrement.ToString());
Console.WriteLine(bruceAsAnimal.Excrement.ToString());
Run Code Online (Sandbox Code Playgroud)

这将输出:"RadioactivePoo"两次,表明多态性尚未被破坏.

进一步阅读

  • 显式接口实现
  • 新修饰符.我没有在这个简化的解决方案中使用它,但您可能需要更复杂的解决方案.例如,如果您想为BaseAnimal创建一个接口,那么您需要在"PooType Excrement"的解除中使用它.
  • out Generic Modifier(Covariance).我再次没有在这个解决方案中使用它,但如果你想做一些像MyType<Poo>从IAnimal返回并MyType<PooType>从BaseAnimal 返回的东西那么你需要使用它来能够在两者之间进行转换.

  • 老兄,这可能超级酷.我现在没有时间进一步分析,但看起来你可能已经得到了它,如果你确实破解了这个,那么高五,并感谢分享.不幸的是,这种"便便"和"粪便"业务是一个很大的分心. (4认同)
  • 我已经耕种了 48 小时,这个答案刚刚破解了它。我就像......“伙计,这可能非常酷。” (2认同)

Mar*_*ell 11

C#9 为我们提供了协变覆盖返回类型。基本上:你想要的就行


Ras*_*ber 9

还有这个选项(显式接口实现)

public class Cat:Animal
{
  Poo Animal.Excrement { get { return Excrement; } }
  public RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
}
Run Code Online (Sandbox Code Playgroud)

你失去了使用基类来实现Cat的能力,但在正面,你保持了Cat和Dog之间的多态性.

但我怀疑增加的复杂性是值得的.