C#:覆盖返回类型


80

有没有办法在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为例如,Cat而仍然可以成为Animal用户不一定了解或关心其放射性便便的列表的一部分。希望有道理...

据我所知,编译器至少不允许这样做。所以我想这是不可能的。但是您对此有什么建议呢?


2
泛型呢?他们不会帮忙吗?
阿尼斯·拉普萨09年

3
Cat.Excrement()不应该只是将RadioActivePoo的实例返回并作为Poo吗?您有一个通用的界面,请使用它。(并感谢您的歇斯底里的例子。)
hometoast

1
我也想对这个例子表示感谢:下面的@goodgai建议创建抽象的Poo-我不知道如何向非程序员解释这一点……
Paolo Tedesco,2009年

1
@Konrad:有点不确定我是否应该只是嘲笑那个精彩的评论,或者我是否也应该问一下它是否实际上是一种非常可怕的代码气味,应该指出(在这种情况下,我想了解一下: p)
Svish

3
我对自己发现这些示例的有趣程度感到几乎失望XD
Gurgadurgen 2014年

Answers:


22

我知道已经有很多解决此问题的解决方案,但我想我想出了一个解决现有解决方案中存在的问题的解决方案。

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

  • 保罗·泰德斯科(Paolo Tedesco)的第一个解决方案:猫和狗没有共同的基类。
  • 保罗·特德斯科(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> { }

使用此解决方案,您无需覆盖“狗”或“猫”中的任何内容!这是一些示例用法:

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

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

进一步阅读

  • 显式接口实现
  • 新的修改器。我没有在此简化的解决方案中使用它,但在更复杂的解决方案中可能需要它。例如,如果您想为BaseAnimal创建一个接口,则需要在“ PooType Excrement”的简化中使用它。
  • 出通用修饰符(协方差)。同样,在此解决方案中我没有使用它,但是如果您想执行类似的操作,例如MyType<Poo>从IAnimal返回并MyType<PooType>从BaseAnimal返回,则需要使用它才能在两者之间进行转换。

6
杜德,这可能太酷了。我目前没有时间进一步分析,但是看来您可能已经掌握了,如果您确实做到了,请高五并感谢您的分享。不幸的是,这种“便便”和“娱乐”业务是一个很大的干扰。
尼古拉斯·彼得森

1
我想我找到了一个相关问题的解决方案,其中方法必须返回继承的类型-即“狗”中从“动物”继承的方法仍然返回Dog(此),而不是Animal。它是通过扩展方法完成的,我可能会在这里分享。
尼古拉斯·彼得森

关于类型安全性,bruce.Excement和bruceAsAnimal.Excrement都将是RadioactivePoo类型的?
Anestis Kivranoglou

@AnestisKivranoglou是的,两者都将是RadioactivePoo类型的
抢劫

我已经坚持了48个小时,而这个答案已经破解了。我当时就像……“老兄,这可能太酷了。”
Martin Hansen Lennox

50

泛型基类呢?

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:我修改了代码以清楚地表明扩展方法没有以任何方式强制执行以下事实:iPooProvider继承自BaseAnimal。您说“甚至更强类型”是什么意思?


只是在想同样的事情。
琼斯医生

9
您是否不对猫和狗之间的同种异化有所了解?
almog.ori,2009年

如果您还想覆盖其他类型怎么办?从技术上讲,您可能会遇到一大堆类型参数。我想我可能会发现这很烦...但是,是的,这是一个解决方案。
Svish

@Svish:我想一旦发生这种情况,就该使用依赖注入框架了。
布赖恩2009年

StronglyTypedExcrement方法如何知道iPooProvider是BaseAnimal?它只是猜测吗?还是我看不到的东西?可以使该方法的类型更强吗?
Svish

31

尽管有些人希望,这称为返回类型协方差,并且通常在C#或.NET中不受支持。

我要做的是保持相同的签名,但ENSURE在派生类中添加一个附加子句,在该派生类中,我确保该子句返回a RadioActivePoo。因此,简而言之,我会通过契约进行设计,而我无法通过语法进行设计。

其他人更喜欢假货它。可以,但是我倾向于节省“基础结构”代码行。如果代码的语义足够清晰,那么我很高兴,尽管不是编译时机制,但通过合同设计可以使我实现这一目标。

泛型相同, 其他答案也建议这样做。我会把它们用作比返回放射性便便更好的理由-但这就是我。


什么是ENSURE子句?那将如何工作?是.Net中的属性吗?
Svish

1
在.Net中,在看到.Net 4.0的代码约定之前,我将ENSURE(x)子句简单地写为“ Debug.Assert(x)”。如需进一步参考,参见例如archive.eiffel.com/doc/manuals/technology/contract/page.html或面向对象软件工程,第二版,由伯特兰·迈耶(1994年)第11章对象
丹尼尔Daranas

5
“我会比返回放射性便便更好的理由来使用它们-但这就是我”属于我自己喜欢的引文列表:)
Daniel Daranas 2010年

9

也有此选项(显式接口实现)

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

您失去了使用基类实现Cat的能力,但从好的方面来说,您保留了Cat和Dog之间的多态性。

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


4

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

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

例如:

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

这个例子使这一点变得很难理解。但是很棒的代码!
rposky

2

如果我错了,请纠正我,但是如果不是多义的话,如果它继承自Poo,就不是可以返回RadioActivePoo的全部,合同将与抽象类相同,只是返回RadioActivePoo()


2
您说得很对,但是他希望避免多余的使用和更强的键入功能,这主要是仿制药的用途……
Paolo Tedesco,2009年

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

Animal界面在这里有什么意义?没有什么可以继承。
2012年

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

阿克,没关系。我没有意识到hjb417已经发布了类似的解决方案。至少我的不需要修改基类。
Cybis

您的“解决方案”确实破坏了多态性,因此它并不是真正的解决方案。另一方面,hjb解决方案是真正的解决方案,非常聪明,恕我直言。
greenoldman

0

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


0

仅供参考。这在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


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

0

您可以只使用返回接口。就您而言,是IPoo。

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


0

以下内容结合了其他几个答案的一些最佳方面,以及一种允许Cat具有Excrement所需RadioactivePoo类型的属性的关键方面的技术,但是能够将其返回,就像Poo仅知道我们拥有一个AnimalBase而不是一个而不是特别是一个Cat

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

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

如果Cat是唯一Poo以某种方式具有特殊性的动物,或者我们不希望的类型Excrement成为a的主要定义特征Cat,则可以在层次结构中跳过中间泛型类,因此可以Cat直接从派生AnimalWithSpecialisations,但是如果存在是几种不同的动物,其主要特征是它们Poo在某种程度上具有特殊性,将“样板”划分为中级类别有助于保持动物的健康。Cat类本身相当干净,尽管这要花费几个额外的虚函数调用的代价。

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

public interface IExcretePoo<out TPoo>
  where TPoo : Poo
{
  TPoo Excrement { get; }
}

public class Poo
{ }

public class RadioactivePoo : Poo
{ }

public class AnimalBase : IExcretePoo<Poo>
{
  public virtual Poo Excrement { get { return new Poo(); } }
}

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

public abstract class AnimalWithSpecialisations : AnimalBase
{
  // this class connects AnimalBase to AnimalWithSpecialPoo<TPoo>
  public sealed override Poo Excrement { get { return SpecialPoo; } }

  // if not overridden, our "special" poo turns out just to be normal animal poo...
  protected virtual Poo SpecialPoo { get { return base.Excrement; } }
}

public abstract class AnimalWithSpecialPoo<TPoo> : AnimalWithSpecialisations, IExcretePoo<TPoo>
  where TPoo : Poo
{
  sealed protected override Poo SpecialPoo { get { return Excrement; } }
  public new abstract TPoo Excrement { get; }
}

public class Cat : AnimalWithSpecialPoo<RadioactivePoo>
{
  public override RadioactivePoo 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.Excrement;

    AnimalBase animal = cat;

    Poo animalPoo = catPoo;
    animalPoo = animal.Excrement;

    AnimalWithSpecialPoo<RadioactivePoo> radioactivePooingAnimal = cat;
    RadioactivePoo radioactivePoo = radioactivePooingAnimal.Excrement;

    IExcretePoo<Poo> pooExcreter = cat; // through this interface we don't know the Poo was radioactive.
    IExcretePoo<RadioactivePoo> radioactivePooExcreter = cat; // through this interface we do.

    // we can replace these with the dog equivalents:
    animal = dog;
    animalPoo = dogPoo;
    pooExcreter = dog;

    // but we can't do:
    // radioactivePooExcreter = dog;
    // radioactivePooingAnimal = dog;
    // radioactivePoo = dogPoo;
  }

0

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


-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影响性能,因此请避免使用它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.