C#:覆盖返回类型

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1048884/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 06:58:27  来源:igfitidea点击:

C#: Overriding return types

c#inheritancetypescovarianceoverriding

提问by Svish

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?

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

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

RadioactivePooof course inherits from Poo.

RadioactivePoo当然继承自Poo.

My reason for wanting this is so that those who use Catobjects could use the Excrementproperty without having to cast the Poointo RadioactivePoowhile for example the Catcould still be part of an Animallist where users may not necessarily be aware or care about their radioactive poo. Hope that made sense...

我想要这个的原因是,使用Cat对象的人可以使用该Excrement属性而不必将其强制Poo转换,RadioactivePoo而例如Cat仍然可能是Animal列表的一部分,用户可能不一定知道或关心他们的放射性粪便。希望这是有道理的...

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?

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

采纳答案by rob

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.
  • Paolo Tedesco 的第一个解决方案:Cat 和 Dog 没有共同的基类。
  • Paolo Tedesco 的第二个解决方案:有点复杂,难以阅读。
  • Daniel Daranas 的解决方案:可行,但它会因大量不必要的强制转换和 Debug.Assert() 语句而使您的代码变得混乱。
  • hjb417 的解决方案:此解决方案不允许您将逻辑保留在基类中。这个例子中的逻辑非常简单(调用构造函数),但在现实世界的例子中则不然。

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:

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

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.

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

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

回答by bobbyalex

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

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

回答by Paolo Tedesco

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...

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

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

第二次编辑@Svish:我修改了代码以明确显示扩展方法没有以任何方式强制执行iPooProviderBaseAnimal. 你所说的“更强类型”是什么意思?

回答by almog.ori

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

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

回答by Daniel Daranas

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

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

What I would do is keep the same signature but add an additional ENSUREclause 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.

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

Others prefer to fakeit 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 answerssuggest. I would use them for a better reason than just returning radioactive poo - but that's just me.

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

回答by Shiraz Bhaiji

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

回答by Rasmus Faber

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.

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

But I doubt the added complexity is worth it.

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

回答by Hasani Blackwell

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.

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

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

回答by Cybis

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.

我想我找到了一种不依赖于泛型或扩展方法,而是方法隐藏的方法。但是,它可能会破坏多态性,因此如果您进一步从 Cat 继承,请特别小心。

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

我希望这篇文章仍然可以帮助某人,尽管晚了 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; } }
}

回答by jedesah

FYI. This is implemented quite easily in Scala.

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

这是您可以使用的现场示例:http: //www.scalakata.com/50d0d6e7e4b0a825d655e832