Pregunta

¿Hay alguna forma de anular los tipos de retorno en C #? En caso afirmativo, ¿cómo, y si no, por qué y qué es una forma recomendada de hacerlo?

Mi caso es que tengo una interfaz con una clase base abstracta y sus descendientes. Me gustaría hacer esto (ok, no realmente, ¡pero 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 Animal en <=> mientras que, por ejemplo, el <=> aún podría ser parte de un <=> lista en la que los usuarios no necesariamente deben conocer o preocuparse por su caca radiactiva. Espero que tenga sentido ...

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

¿Fue útil?

Solución

¿Qué pasa con 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> { }

EDITAR : 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 manera, tanto el perro como el gato heredan de Animal (como se señaló en los comentarios, mi primera solución no conservó la herencia).
Es necesario marcar explícitamente las clases con la interfaz del marcador, lo cual es doloroso, pero tal vez esto podría darle algunas ideas ...

SEGUNDA EDICIÓN @Svish: Modifiqué el código para mostrar explícitamente que el método de extensión no está aplicando de ninguna manera el hecho de que iPooProvider hereda de BaseAnimal. ¿Qué quieres decir con & Quot; aún más fuertemente tipeado & Quot ;?

Otros consejos

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

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

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

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

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 satisfecho con algunas de las soluciones existentes por las siguientes razones:

  • La primera solución de Paolo Tedesco: Cat and 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 saturaría su código con una gran cantidad de declaraciones innecesarias de conversió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 necesita anular nada en Dog OR Cat! Aquí hay algunos ejemplos de uso:

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

Esto generará: " RadioactivePoo " dos veces, lo que muestra que el polimorfismo no se ha roto.

Lecturas adicionales

  • Implementación explícita de la interfaz
  • nuevo Modificador . No lo utilicé en esta solución simplificada, pero es posible que lo necesite en una solución más complicada. Por ejemplo, si desea crear una interfaz para BaseAnimal, deberá usarla en su declaración de & Quot; PooType Excrement & Quot ;.
  • out Modificador genérico (covarianza) . Una vez más, no lo usé en esta solución, pero si desea hacer algo como devolver MyType<Poo> de IAnimal y devolver MyType<PooType> de BaseAnimal, deberá usarlo para poder lanzar entre 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 capacidad 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 valga la pena.

¿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? Luego, las clases derivadas pueden anular el tipo de retorno de la clase base.

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

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

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

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

Creo que he encontrado una forma que no depende de los métodos genéricos o de extensión, sino de la ocultación de métodos. 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 llegar con 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; } }
}

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

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

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

Podría usar una devolución de una interfaz. En tu caso, IPoo.

Esto es preferible a usar un tipo genérico, en su caso, porque está usando una clase base de comentarios.

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 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 API que escribí para mi empresa. Si planea desarrollar una API, esto tiene potencial para mejorar la usabilidad y la experiencia de desarrollo en algunos casos de uso. Sin embargo, el uso de <=> tiene un impacto en el rendimiento, así que trate de evitarlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top