C # : 覆盖返回类型

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

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

62084 次浏览

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

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"?

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

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.

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

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.

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

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

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.

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

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

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.

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.

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

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