c# inheritance types covariance override

C#: Anulando los tipos de retorno



inheritance types (13)

¿Hay alguna manera de anular los tipos de devolución en C #? Si es así, ¿cómo y si no, por qué y cuál es una forma recomendada de hacerlo?

Mi caso es que tengo una interfaz con una clase base abstracta y descendientes de eso. Me gustaría hacer esto (¡vale, no realmente, sino como ejemplo!):

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 por supuesto hereda de Poo .

Mi razón para querer esto es para que aquellos que usan objetos Cat puedan usar la propiedad Excrement sin tener que convertir el Poo en RadioactivePoo mientras que por ejemplo, Cat podría formar parte de una lista de Animal en la que los usuarios no necesariamente estén al tanto o no se preocupen por su poo radioactivo Espero que tenga sentido ...

Por lo que puedo ver, el compilador no permite esto al menos. Así que supongo que es imposible. Pero, ¿qué recomendarías como solución a esto?


¿Por qué no definir un método virtual protegido que crea el ''Excremento'' y mantener la propiedad pública que devuelve el ''Excremento'' no virtual. Entonces las clases derivadas pueden anular el tipo de devolución de la clase base.

En el siguiente ejemplo, hago ''Excrement'' no virtual pero proporciono la propiedad ExcrementImpl para permitir que las clases derivadas proporcionen el ''Poo'' apropiado. Los tipos derivados pueden anular el tipo de devolución de ''Excrement'' ocultando la implementación de la clase base.

Ex:

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


¿Qué tal una clase base genérica?

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 : una nueva solución, utilizando métodos de extensión y una interfaz de marcador ...

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

De esta forma, tanto Dog como Cat heredan de Animal (como se comentó en los comentarios, mi primera solución no conservaba la herencia).
Es necesario marcar explícitamente las clases con la interfaz del marcador, lo cual es doloroso, pero tal vez esto podría darte algunas ideas ...

SEGUNDA EDICION @Svish: modifiqué el código para mostrar explícitamente que el método de extensión no está haciendo cumplir de ninguna manera el hecho de que iPooProvider hereda de BaseAnimal . ¿A qué te refieres con "aún más fuertemente tipado"?


Bueno, en realidad es posible devolver un tipo concreto que varía del tipo de retorno heredado (incluso para métodos estáticos), gracias a la 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); } }

Implementé esto en un contenedor de API que escribí para mi empresa. Si planea desarrollar una API, esto tiene el potencial de mejorar la usabilidad y la experiencia de desarrollo en algunos casos de uso. Sin embargo, el uso de la dynamic tiene un impacto en el rendimiento, así que trate de evitarlo.


Corrígeme si estoy equivocado pero no es todo el sentido del polimorfismo para poder devolver RadioActivePoo si hereda de Poo, el contrato sería lo mismo que la clase abstracta, pero simplemente devuelve RadioActivePoo ()


Creo que he encontrado una forma que no depende de los genéricos o los métodos de extensión, sino más bien el método de ocultación. Sin embargo, puede romper el polimorfismo, así que tenga especial cuidado si hereda más de Cat.

Espero que esta publicación aún pueda ayudar a alguien, a pesar de tener 8 meses de retraso.

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


Creo que su respuesta se llama covarianza.

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


Esto se llama covarianza de tipo de retorno y no es compatible con C # o .NET en general, a pesar de los wishes algunas personas.

Lo que haría sería mantener la misma firma pero agregar una cláusula ENSURE adicional a la clase derivada en la que me asegure de que esta devuelva un RadioActivePoo . Entonces, en resumen, haría mediante el diseño por contrato lo que no puedo hacer a través de la sintaxis.

Otros prefieren fake su lugar. Está bien, supongo, pero tiendo a economizar las líneas de código de "infraestructura". Si la semántica del código es lo suficientemente clara, estoy contento, y el diseño por contrato me permite lograr eso, aunque no es un mecanismo de tiempo de compilación.

Lo mismo para genéricos, que otras respuestas sugieren. Los usaría por una razón mejor que simplemente devolver la caca radiactiva, pero así soy yo.


FYI. Esto se implementa con bastante facilidad en 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)

Aquí hay un ejemplo en vivo con el que puedes jugar: http://www.scalakata.com/50d0d6e7e4b0a825d655e832


Podría ayudar si RadioactivePoo se deriva de poo y luego usa genéricos.


Podrías usar una interfaz de retorno. En tu caso, IPoo.

Esto es preferible al uso de un tipo genérico, en su caso, porque está utilizando una clase base de comentarios.


Prueba esto:

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


Sé que ya hay muchas soluciones para este problema, pero creo que se me ocurrió una que soluciona los problemas que tenía con las soluciones existentes.

No estaba contento con algunas de las soluciones existentes por las siguientes razones:

  • La primera solución de Paolo Tedesco: Cat y Dog no tienen una clase base común.
  • La segunda solución de Paolo Tedesco: es un poco complicado y difícil de leer.
  • La solución de Daniel Daranas: esto funciona, pero complicaría tu código con una gran cantidad de sentencias innecesarias de fundición y Debug.Assert ().
  • Soluciones de hjb417: esta solución no le permite mantener su lógica en una clase base. La lógica es bastante trivial en este ejemplo (llamar a un constructor) pero en un ejemplo del mundo real no lo sería.

Mi solución

Esta solución debería superar todos los problemas que mencioné anteriormente mediante el uso de genéricos y ocultación de métodos.

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

¡Con esta solución no es necesario que anule nada en Dog or Cat! ¿Cuan genial es eso? Aquí hay un poco de uso de muestra:

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

Esto dará como resultado: "RadioactivePoo" dos veces que muestra que el polimorfismo no se ha roto.

Otras lecturas

  • Implementación de interfaz explícita
  • nuevo modificador No lo utilicé en esta solución simplificada, pero puede necesitarlo en una solución más complicada. Por ejemplo, si desea crear una interfaz para BaseAnimal, deberá usarla en su declaración de "PooType Excrement".
  • out Modificador genérico (Covarianza) . De nuevo, no lo MyType<Poo> en esta solución, pero si quisiera hacer algo como devolver MyType<Poo> de IAnimal y devolver MyType<PooType> de BaseAnimal, entonces necesitaría usarlo para poder convertir los dos.

También existe esta opción (implementación de interfaz explícita)

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

Pierdes la habilidad de usar la clase base para implementar Cat, pero en el lado positivo, mantienes el polimorfismo entre Cat y Dog.

Pero dudo que la complejidad agregada lo valga.