C#:覆盖返回类型

时间:2009-06-26 12:29:44

标签: c# inheritance types covariance override

有没有办法覆盖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(); } }
}

RadioactivePoo当然继承自Poo

我想要这样做的原因是,那些使用Cat对象的人可以使用Excrement属性而无需将Poo转换为RadioactivePoo,例如{ {1}}仍然可以成为Cat列表的一部分,用户可能不一定知道或关心他们的放射性便便。希望有意义......

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

15 个答案:

答案 0 :(得分:46)

通用基类怎么样?

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

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

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();
    }
}

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

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

答案 1 :(得分:29)

这称为返回类型协方差,并且一般不支持C#或.NET,尽管有些人wishes

我要做的是保持相同的签名,但在派生类中添加一个额外的ENSURE子句,我确保在该类中返回RadioActivePoo。所以,简而言之,我通过合同进行设计,而不是通过语法来做。

其他人更愿意改为fake。我猜,没关系,但我倾向于节省“基础设施”代码行。如果代码的语义足够清楚,我很高兴,通过契约设计让我实现了这一点,尽管它不是编译时机制。

同样适用于泛型,other answers建议。我会使用它们的原因不仅仅是返回放射性便便 - 但那只是我。

答案 2 :(得分:17)

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

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

  • Paolo Tedesco的第一个解决方案: Cat and Dog没有共同的基类。
  • 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> { }

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

Cat bruce = new Cat();
IAnimal bruceAsAnimal = bruce as IAnimal;
Console.WriteLine(bruce.Excrement.ToString());
Console.WriteLine(bruceAsAnimal.Excrement.ToString());

这将输出:“RadioactivePoo”两次,表明多态性尚未被破坏。

进一步阅读

  • Explicit Interface Implementation
  • new Modifier。我没有在这个简化的解决方案中使用它,但您可能需要更复杂的解决方案。例如,如果您想为BaseAnimal创建一个接口,那么您需要在“PooType Excrement”的去除中使用它。
  • out Generic Modifier (Covariance)。我再次在这个解决方案中没有使用它,但是如果你想做一些像IAnimal返回MyType<Poo>并从BaseAnimal返回MyType<PooType>那样的事情,那么你需要使用它才能在两者之间进行转换

答案 3 :(得分:9)

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

public class Cat:Animal
{
  Poo Animal.Excrement { get { return Excrement; } }
  public RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
}

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

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

答案 4 :(得分:4)

为什么不定义一个创建'Excrement'的受保护虚拟方法,并保留返回'Excrement'非虚拟的公共属性。然后派生类可以覆盖基类的返回类型。

在下面的示例中,我将'Excrement'设置为非虚拟但提供属性ExcrementImpl以允许派生类提供正确的'Poo'。然后,派生类型可以通过隐藏基类实现来覆盖“Excrement”的返回类型。

E.x:

namepace ConsoleApplication8

{
public class Poo { }

public class RadioactivePoo : Poo { }

public interface Animal
{
    Poo Excrement { get; }
}

public class AnimalBase
{
    public Poo Excrement { get { return ExcrementImpl; } }

    protected virtual Poo ExcrementImpl
    {
        get { return new Poo(); }
    }
}

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

public class Cat : AnimalBase
{
    protected override Poo ExcrementImpl
    {
        get { return new RadioactivePoo(); }
    }

    public new RadioactivePoo Excrement { get { return (RadioactivePoo)ExcrementImpl; } }
}
}

答案 5 :(得分:2)

纠正我,如果我错了,但不是pollymorphism的全部要点,如果它继承自Poo,能够返回RadioActivePoo,合同将与抽象类相同,但只返回RadioActivePoo()

答案 6 :(得分:2)

试试这个:

namespace ClassLibrary1
{
    public interface Animal
    {   
        Poo Excrement { get; }
    }

    public class Poo
    {
    }

    public class RadioactivePoo
    {
    }

    public class AnimalBase<T>
    {   
        public virtual T Excrement
        { 
            get { return default(T); } 
        }
    }


    public class Dog : AnimalBase<Poo>
    {  
        // No override, just return normal poo like normal animal
    }

    public class Cat : AnimalBase<RadioactivePoo>
    {  
        public override RadioactivePoo Excrement 
        {
            get { return new RadioactivePoo(); } 
        }
    }
}

答案 7 :(得分:1)

我认为我发现了一种不依赖于泛型或扩展方法的方法,而是方法隐藏方法。但是,它可以打破多态性,因此如果您继续从Cat继承,请特别小心。

我希望这篇文章能够帮助某人,尽管已经晚了8个月。

public interface Animal
{
    Poo Excrement { get; }
}

public class Poo
{
}

public class RadioActivePoo : Poo
{
}

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

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

public class CatBase : AnimalBase
{
    public override Poo Excrement { get { return new RadioActivePoo(); } }
}

public class Cat : CatBase
{
    public new RadioActivePoo Excrement { get { return (RadioActivePoo) base.Excrement; } }
}

答案 8 :(得分:0)

如果RadioactivePoo是从poo派生出来然后使用泛型,那可能会有所帮助。

答案 9 :(得分:0)

FYI。这在Scala中很容易实现。

trait Path

trait Resource
{
    def copyTo(p: Path): Resource
}
class File extends Resource
{
    override def copyTo(p: Path): File = new File
    override def toString = "File"
}
class Directory extends Resource
{
    override def copyTo(p: Path): Directory = new Directory
    override def toString = "Directory"
}

val test: Resource = new Directory()
test.copyTo(null)

以下是您可以使用的实时示例:http://www.scalakata.com/50d0d6e7e4b0a825d655e832

答案 10 :(得分:0)

我相信你的答案叫做协方差。

class Program
{
    public class Poo
    {
        public virtual string Name { get{ return "Poo"; } }
    }

    public class RadioactivePoo : Poo
    {
        public override string Name { get { return "RadioactivePoo"; } }
        public string DecayPeriod { get { return "Long time"; } }
    }

    public interface IAnimal<out T> where T : Poo
    {
        T Excrement { get; }
    }

    public class Animal<T>:IAnimal<T> where T : Poo 
    {
        public T Excrement { get { return _excrement ?? (_excrement = (T) Activator.CreateInstance(typeof (T), new object[] {})); } } 
        private T _excrement;
    }

    public class Dog : Animal<Poo>{}
    public class Cat : Animal<RadioactivePoo>{}

    static void Main(string[] args)
    {
        var dog = new Dog();
        var cat = new Cat();

        IAnimal<Poo> animal1 = dog;
        IAnimal<Poo> animal2 = cat;

        Poo dogPoo = dog.Excrement;
        //RadioactivePoo dogPoo2 = dog.Excrement; // Error, dog poo is not RadioactivePoo.

        Poo catPoo = cat.Excrement;
        RadioactivePoo catPoo2 = cat.Excrement;

        Poo animal1Poo = animal1.Excrement;
        Poo animal2Poo = animal2.Excrement;
        //RadioactivePoo animal2RadioactivePoo = animal2.Excrement; // Error, IAnimal<Poo> reference do not know better.


        Console.WriteLine("Dog poo name: {0}",dogPoo.Name);
        Console.WriteLine("Cat poo name: {0}, decay period: {1}" ,catPoo.Name, catPoo2.DecayPeriod);
        Console.WriteLine("Press any key");

        var key = Console.ReadKey();
    }
}

答案 11 :(得分:0)

您可以使用返回接口。在你的情况下,IPoo。

在您的情况下,这比使用泛型类型更可取,因为您使用的是注释基类。

答案 12 :(得分:0)

以下内容结合了其他几个答案的一些最佳方面以及一种使Cat的关键方面具有所需Excrement类型的RadioactivePoo属性的技术,但是如果我们只知道我们有Poo而不是专门的AnimalBase,就可以只返回Cat

即使实现中存在调用程序,也不需要调用程序使用泛型,也不需要调用命名不同的函数来获得特殊的Poo

中间类AnimalWithSpecialisations仅用于密封Excrement属性,并通过非公共SpecialPoo属性将其连接到派生类AnimalWithSpecialPoo<TPoo>,该派生类具有{{ 1}}属性的派生返回类型。

如果Excrement是唯一的Cat有特殊意义的动物,或者我们不希望Poo的类型成为{{1 }},可以在层次结构中跳过中间类,以便Excrement直接从Cat派生,但是如果存在几种不同的动物,它们的主要特征是它们的Cat是特殊的在某种程度上,将“样板”划分为中间类有助于使AnimalWithSpecialisations类本身保持整洁,尽管这需要花费几个额外的虚函数调用。

示例代码显示大多数预期的操作都“按预期”进行。

Poo

答案 13 :(得分:0)

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

答案 14 :(得分:-1)

好吧,实际上可以返回一个具体类型,它与继承的返回类型不同(即使是静态方法),这要感谢dynamic

public abstract class DynamicBaseClass
{
    public static dynamic Get (int id) { throw new NotImplementedException(); }
}

public abstract class BaseClass : DynamicBaseClass
{
    public static new BaseClass Get (int id) { return new BaseClass(id); }
}

public abstract class DefinitiveClass : BaseClass
{
    public static new DefinitiveClass Get (int id) { return new DefinitiveClass(id);
}

public class Test
{
    public static void Main()
    {
        var testBase = BaseClass.Get(5);
        // No cast required, IntelliSense will even tell you
        // that var is of type DefinitiveClass
        var testDefinitive = DefinitiveClass.Get(10);
    }
}

我在我为公司编写的API包装器中实现了这一点。如果您计划开发API,则有可能在某些用例中提高可用性和开发经验。然而,dynamic的使用会对性能产生影响,因此请尽量避免使用。