89

有没有办法覆盖 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用户可能不一定知道或关心他们的放射性便便的列表的一部分。希望这是有道理的...

据我所知,编译器至少不允许这样做。所以我想这是不可能的。但是你会推荐什么来解决这个问题?

4

15 回答 15

51

泛型基类呢?

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. 您所说的“甚至更强类型”是什么意思?

于 2009-06-26T12:35:43.900 回答
35

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

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

  • Paolo Tedesco 的第一个解决方案: Cat 和 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”两次,表明多态性没有被破坏。

延伸阅读

  • 显式接口实现
  • 新修饰符。我没有在这个简化的解决方案中使用它,但您可能需要在更复杂的解决方案中使用它。例如,如果您想为 BaseAnimal 创建一个接口,那么您需要在“PooType Excrement”的声明中使用它。
  • 通用修饰符(协方差)。同样,我没有在这个解决方案中使用它,但是如果你想做一些事情,比如MyType<Poo>从 IAnimal 返回并从 BaseAnimal 返回MyType<PooType>,那么你需要使用它才能在两者之间进行转换。
于 2012-12-05T05:43:17.897 回答
33

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

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

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

泛型也是如此,其他答案表明了这一点。我会出于更好的理由使用它们,而不仅仅是返回放射性便便 - 但这只是我。

于 2009-06-26T12:42:22.123 回答
10

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

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

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

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

于 2009-06-26T13:21:53.773 回答
4

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

在以下示例中,我将“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; } }
}
}
于 2009-10-01T12:59:32.783 回答
4

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

于 2020-05-20T21:51:50.477 回答
2

如果我错了,请纠正我,但如果它继承自 Poo,那么多态性的全部意义不是能够返回 RadioActivePoo,合同将与抽象类相同,但只返回 RadioActivePoo()

于 2009-06-26T12:37:10.620 回答
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(); } 
        }
    }
}
于 2009-06-26T12:53:33.783 回答
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; } }
}
于 2010-02-20T02:43:48.923 回答
0

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

于 2009-06-26T12:35:14.023 回答
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();
    }
}
于 2013-03-27T22:26:56.360 回答
0

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

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

于 2015-05-28T22:32:09.983 回答
0

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

调用者不需要使用泛型,即使它们存在于实现中,也不需要调用不同名称的函数来获取 special 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;
  }
于 2020-04-08T13:26:33.307 回答
-1

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

于 2012-12-18T20:52:28.283 回答
-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性能有影响,所以尽量避免它。

于 2017-05-02T09:14:19.277 回答