C#:重写返回类型

发布于 2024-07-25 17:37:27 字数 815 浏览 6 评论 0原文

有没有办法重写 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 例如, 仍然可能是动物 列表的一部分,其中用户可能不一定知道或关心他们的放射性粪便。 希望这是有道理的......

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

Is there way to override return types in C#? If so how, and if not why and what is a recommended way of doing it?

My case is that I have an interface with an abstract base class and descendants of that. I would like to do this (ok not really, but as an example!) :

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 of course inherits from Poo.

My reason for wanting this is so that those who use Cat objects could use the Excrement property without having to cast the Poo into RadioactivePoo while for example the Cat could still be part of an Animal list where users may not necessarily be aware or care about their radioactive poo. Hope that made sense...

As far as I can see the compiler doesn't allow this at least. So I guess it is impossible. But what would you recommend as a solution to this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

悲喜皆因你 2024-08-01 17:37:27

通用基类怎么样?

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 (正如评论中所述,我的第一个解决方案没有保留继承)。< br>
有必要使用标记接口显式标记类,这很痛苦,但这也许可以给您一些想法...

第二次编辑 @Svish:我修改了代码以明确显示扩展方法不以任何方式强制执行 iPooProvider 继承自 BaseAnimal 的事实。 “更强类型”是什么意思?

What about a generic base class?

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

EDIT: A new solution, using extension methods and a marker interface...

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

This way Dog and Cat both inherit from Animal (as remarked in the comments, my first solution did not preserve the inheritance).
It's necessary to mark explicitly the classes with the marker interface, which is painful, but maybe this could give you some ideas...

SECOND EDIT @Svish: I modified the code to show explitly that the extension method is not enforcing in any way the fact that iPooProvider inherits from BaseAnimal. What do you mean by "even more strongly-typed"?

抠脚大汉 2024-08-01 17:37:27

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

我对现有的一些解决方案并不满意,原因如下:

  • 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”两次,这表明多态性尚未被破坏。

进一步阅读

I know there are a lot of solutions for this problem already but I think I've come up with one that fixes the issues I had with the existing solutions.

I wasn't happy with the some of the existing solutions for the following reasons:

  • Paolo Tedesco's first solution: Cat and Dog do not have a common base class.
  • Paolo Tedesco's second solution: It is a bit complicated and hard to read.
  • Daniel Daranas's solution: This works but it would clutter up your code with a lot of unnecessary casting and Debug.Assert() statements.
  • hjb417's solutions: This solution doesn't let you keep your logic in a base class. The logic is pretty trivial in this example (calling a constructor) but in a real world example it wouldn't be.

My Solution

This solution should overcome all of the issues I mentioned above by using both generics and method hiding.

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

With this solution you don't need to override anything in Dog OR Cat! Here is some sample usage:

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

This will output: "RadioactivePoo" twice which shows that polymorphism has not been broken.

Further Reading

  • Explicit Interface Implementation
  • new Modifier. I didn't use it in this simplified solution but you may need it in a more complicated solution. For example if you wanted to create an interface for BaseAnimal then you would need to use it in your decleration of "PooType Excrement".
  • out Generic Modifier (Covariance). Again I didn't use it in this solution but if you wanted to do something like return MyType<Poo> from IAnimal and return MyType<PooType> from BaseAnimal then you would need to use it to be able to cast between the two.
(り薆情海 2024-08-01 17:37:27

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

我要做的就是保留相同的签名,但向派生类添加一个额外的 ENSURE 子句,以确保该子句返回一个 RadioActivePoo。 所以,简而言之,我会通过契约设计来完成我无法通过语法完成的事情。

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

对于泛型也是如此,其他答案建议这样做。 我使用它们的原因不仅仅是归还放射性粪便——但这只是我的想法。

This is called return type covariance and is not supported in C# or .NET in general, despite some people's wishes.

What I would do is keep the same signature but add an additional ENSURE clause to the derived class in which I ensure that this one returns a RadioActivePoo. So, in short, I'd do via design by contract what I can't do via syntax.

Others prefer to fake it instead. It's ok, I guess, but I tend to economize "infrastructure" lines of code. If the semantics of the code are clear enough, I'm happy, and design by contract lets me achieve that, although it is not a compile time mechanism.

The same for generics, which other answers suggest. I would use them for a better reason than just returning radioactive poo - but that's just me.

扬花落满肩 2024-08-01 17:37:27

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

C#9 gives us covariant override return types. Basically: what you want just works.

歌入人心 2024-08-01 17:37:27

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

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

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

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

There is also this option (explicit interface-implementation)

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

You lose the ability to use the base-class to implement Cat, but on the plus-side, you keep the polymorphism between Cat and Dog.

But I doubt the added complexity is worth it.

行至春深 2024-08-01 17:37:27

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

在下面的示例中,我将“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; } }
}
}

Why not define a protected virtual method that creates the 'Excrement' and keep the public property that returns the 'Excrement' non virtual. Then derived classes can override the return type of the base class.

In the following example, I make 'Excrement' non-virtual but provide the property ExcrementImpl to allow derived classes to provide the proper 'Poo'. Derived types can then override the return type of 'Excrement' by hiding the base class implementation.

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; } }
}
}
凉城已无爱 2024-08-01 17:37:27

尝试这个:

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

Try this:

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(); } 
        }
    }
}
断爱 2024-08-01 17:37:27

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

Correct me if im wrong but isnt the whole point of pollymorphism to be able to return RadioActivePoo if it inherits from Poo, the contract would be the same as the abstract class but just return RadioActivePoo()

淡忘如思 2024-08-01 17:37:27

我想我已经找到了一种不依赖于泛型或扩展方法,而是依赖于方法隐藏的方法。 但是,它可能会破坏多态性,因此如果您进一步继承 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; } }
}

I think I've found a way that doesn't depend on generics or extension methods, but rather method hiding. It can break polymorphism, however, so be especially careful if you further inherit from Cat.

I hope this post could still help somebody, despite being 8 months late.

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; } }
}
行雁书 2024-08-01 17:37:27

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

It might help if RadioactivePoo is derived from poo and then use generics.

假情假意假温柔 2024-08-01 17:37:27

我相信你的答案称为协方差。

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

I believe your answer is called covariance.

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();
    }
}
征﹌骨岁月お 2024-08-01 17:37:27

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

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

You could just use a return an Interface. In your case, IPoo.

This is preferable to using a generic type, in your case, because you are using a comment base class.

通知家属抬走 2024-08-01 17:37:27

以下结合了其他几个答案的一些最佳方面以及一种技术,使 Cat 的关键方面具有所需 RadioactivePoo< 的 Excrement 属性/code> 类型,但如果我们只知道我们有一个 AnimalBase 而不是专门的 Cat,则能够将其仅返回为 Poo

调用者不需要使用泛型,即使它们存在于实现中,也不需要调用不同名称的函数来获取特殊的 Poo

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

如果 Cat 是唯一一个其 Poo 在任何方面都是特殊的动物,或者我们不希望 Excrement 的类型成为主要定义Cat 的特征,可以在层次结构中跳过中间泛型类,以便 Cat 直接从 AnimalWithSpecializations 派生,但如果有多个不同的动物,其主要特征是它们的 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;
  }

The following combines some of the best aspects of several other answers as well as a technique to allow the key aspect of Cat having an Excrement property of the required RadioactivePoo type, but being able to return that as merely Poo if we only know we've got an AnimalBase rather than specifically a Cat.

Callers aren't required to use generics, even though they are present in the implementations, nor to call a differently-named function to get special Poo.

The intermediate class AnimalWithSpecialisations serves only to seal the Excrement property, connecting it via a non-public SpecialPoo property to the derived class AnimalWithSpecialPoo<TPoo> which has an Excrement property of a derived return type.

If Cat is the only animal whose Poo is special in any way, or we don't want the type of Excrement to be the main defining feature of a Cat, the intermediate generic class could be skipped in the hierarchy, so that Cat derives directly from AnimalWithSpecialisations, but if there are several different animals whose primary characteristic is that their Poo is special in some way, separating out the "boilerplate" into intermediate classes helps to keep the Cat class itself fairly clean, albeit at the cost of a couple of extra virtual function calls.

The example code shows that most of the expected operations work "as expected".

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;
  }
爱要勇敢去追 2024-08-01 17:37:27

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

FYI. This is implemented quite easily in 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)

Here is a live example you can play with: http://www.scalakata.com/50d0d6e7e4b0a825d655e832

青丝拂面 2024-08-01 17:37:27

好吧,实际上可以返回一个与继承的返回类型不同的具体类型(即使对于静态方法),这要归功于 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会对性能产生影响,所以尽量避免使用。

Well, it is actually possible to return a concrete Type which varies from the inherited return Type (even for static methods), thanks to 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);
    }
}

I implemented this in an API wrapper I wrote for my company. If you plan to develop an API, this has potential to improve the usability and dev experience in some use cases. Nevertheless, the usage of dynamic has an impact to the performance, so try to avoid it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文