C#:重写返回types

有没有办法在C#中覆盖返回types? 如果是的话,如果不是为什么,什么是推荐的做法呢?

我的情况是,我有一个抽象的基类和后裔的接口。 我想这样做(好吧不是真的,但作为一个例子!):

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当然inheritance了Poo

我想要这样做的理由是,那些使用Cat对象的人可以使用Excrement属性,而不必将Poo投入到RadioactivePoo ,例如Cat仍然可以是Animal列表的一部分,用户可能不一定知道或关心他们放射性便便。 希望这是有道理的…

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

那么generics基类呢?

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

编辑 :一种新的解决scheme,使用扩展方法和标记接口…

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

这样狗和猫都从动物inheritance(正如在评论中指出的,我的第一个解决scheme并没有保留inheritance)。
有必要明确标记与标记界面的类,这是痛苦的,但也许这可能会给你一些想法…

第二次编辑 @Svish:我修改了代码,显示了扩展方法没有以任何方式强制iPooProviderinheritance自BaseAnimal 。 你什么意思是“更强types”?

这被称为返回types协variables ,尽pipe有人愿意 ,但在C#或.NET中并不支持。

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

其他人更喜欢伪造它。 没错,我想,但我倾向于节约“基础设施”的代码行。 如果代码的语义足够清晰,我很高兴,合同devise让我实现这一点,虽然它不是一个编译时机制。

generics也是一样, 其他的答案都是这样的 。 我会使用它们,而不仅仅是返回放射性的便便 – 但这只是我。

我知道这个问题已经有很多解决scheme了,但是我想我已经想出了解决现有解决scheme的问题。

由于以下原因,我对一些现有解决scheme感到不满意:

  • Paolo Tedesco的第一个解决scheme:猫和狗没有共同的基础类。
  • 保罗·特德斯科(Paolo Tedesco)的第二个解决scheme:这有点复杂,难于阅读。
  • 丹尼尔Daranas的解决scheme:这工作,但它会混乱了很多不必要的转换和Debug.Assert()语句的代码。
  • hjb417的解决scheme:这个解决scheme不会让你的逻辑保持在基类中。 这个例子中的逻辑非常微不足道(调用一个构造函数),但在现实世界的例子中,它不会。

我的解决scheme

这个解决scheme应该通过使用generics和方法隐藏来克服上面提到的所有问题。

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

有了这个解决scheme,你不需要重写狗或猫的任何东西! 多么酷啊? 以下是一些示例用法:

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

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

进一步阅读

  • 显式接口实现
  • 新的修改器 。 我没有在这个简化的解决scheme中使用它,但是您可能需要一个更复杂的解决scheme。 例如,如果您想为BaseAnimal创build一个接口,那么您将需要在“PooType排泄物”的删除中使用它。
  • 通用修饰符(协方差) 。 再一次,我没有在这个解决scheme中使用它,但是如果你想做一些像从IAnimal返回MyType<Poo>和从BaseAnimal返回MyType<PooType>那么你将需要使用它能够在两者之间进行转换。

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

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

你失去了使用基类实现Cat的能力,但是从正面来看,你保持了Cat和Dog之间的多态性。

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

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

在下面的例子中,我使'排泄物'非虚拟,但提供属性ExcrementImpl允许派生类提供适当的'Poo'。 派生types可以通过隐藏基类实现来覆盖“排泄物”的返回types。

例如:

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

纠正我,如果我错了,但不是pollymorphism的全部点能够返回RadioActivePoo,如果它inheritancePoo,合同将是抽象类相同,但只是返回RadioActivePoo()

尝试这个:

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

我想我已经find了一种不依赖generics或扩展方法的方法,而是隐藏方法。 它可以打破多态,但是,如果你继续从猫inheritance,要特别小心。

我希望这个职位能够帮助别人,尽pipe迟了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; } } } 

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

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

我相信你的答案被称为协变。

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

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

这比使用genericstypes更适合您,因为您使用的是注释基类。

那么,实际上有可能返回一个具体的types,这个types与inheritance的返回types(甚至是静态方法)有所不同,这要归功于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封装器中实现了这个function。 如果你打算开发一个API,这有可能提高一些用例的可用性和开发经验。 不过, dynamic的使用对性能有影响,所以尽量避免。