C#: remplacer les types de retour

Existe - t-il un moyen de remplacer les types de retour en C#? Si oui, comment, et sinon pourquoi et quelle est la façon recommandée de le faire?

Mon cas est que j'ai une interface avec une classe de base abstraite et des descendants de cela. Je voudrais le faire (ok pas vraiment, mais à titre d'exemple!) :

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 bien sûr, hérite de Poo.

Ma raison de vouloir cela est que ceux qui utilisent des objets Cat puissent utiliser la propriété Excrement sans avoir à lancer le Poo dans RadioactivePoo while par exemple, le Cat pourrait encore faire partie d'une liste Animal où les utilisateurs peuvent ne pas nécessairement être au courant ou se soucier de leur caca radioactif. L'espoir qui fait sens...

Pour autant que je puisse voir le compilateur ne le permet pas au moins. Donc je suppose que c'est impossible. Mais que recommanderiez-vous comme solution?

70
demandé sur Daniel Daranas 2009-06-26 16:29:44

13 réponses

Qu'en est-il d'une classe de base générique?

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 : une nouvelle solution, utilisant des méthodes d'extension et une interface de marqueur...

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 cette façon, le chien et le chat héritent tous deux de L'Animal (comme indiqué dans les commentaires, ma première solution n'a pas préservé l'héritage).
Il est nécessaire de marquer explicitement les classes avec l'interface de marqueur, ce qui est douloureux, mais peut-être que cela pourrait vous donner quelques idées...

Deuxième Édition @Svish: j'ai modifié le code pour montrer explicitement que la méthode d'extension n'applique en aucune façon le fait que iPooProvider hérite de BaseAnimal. Que voulez-vous dire par "Encore plus fortement typé"?

46
répondu Paolo Tedesco 2009-07-15 11:52:45

Ceci est appelé type de retour covariance et n'est pas pris en charge en C# ou.net en général, malgré les souhaits de certaines personnes .

Ce que je ferais est de garder la même signature mais d'ajouter une clause ENSURE supplémentaire à la classe dérivée dans laquelle je m'assure que celle-ci renvoie un RadioActivePoo. Donc, en bref, je ferais via design by contract ce que je ne peux pas faire via la syntaxe.

D'Autres préfèrent faux à la place. C'est ok, je suppose, mais j'ai tendance à économiser des lignes "d'infrastructure" de code. Si la sémantique du code est assez claire, je suis heureux, et design by contract me permet d'y parvenir, bien que ce ne soit pas un mécanisme de compilation.

La même chose pour les génériques, queautres réponses suggèrent. Je les utiliserais pour une meilleure raison que de simplement renvoyer du caca radioactif-mais c'est juste moi.

27
répondu Daniel Daranas 2017-05-23 12:26:03

Je sais qu'il y a déjà beaucoup de solutions à ce problème, mais je pense que j'en ai trouvé une qui résout les problèmes que j'avais avec les solutions existantes.

Je n'étais pas heureux avec certaines des solutions existantes pour les raisons suivantes:

  • la première solution de Paolo Tedesco: Le Chat et le chien n'ont pas de classe de base commune.
  • La deuxième solution de Paolo Tedesco: c'est un peu compliqué et difficile à lire.
  • Daniel La solution de Daranas: cela fonctionne mais cela encombrerait votre code avec beaucoup de casting et de débogage inutiles.Assert () déclarations.
  • les solutions de hjb417: Cette solution ne vous permet pas de garder votre logique dans une classe de base. La logique est assez triviale dans cet exemple (en appelant un constructeur) mais dans un exemple réel, ce ne serait pas le cas.

Ma Solution

Cette solution devrait surmonter tous les problèmes que j'ai mentionnés ci-dessus en utilisant à la fois des génériques et méthode de masquage.

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

Avec cette solution, vous n'avez pas besoin de remplacer quoi que ce soit dans le chien ou le chat! C'est pas cool ça? Voici un exemple d'utilisation:

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

Cela affichera: "RadioactivePoo" deux fois ce qui montre que le polymorphisme n'a pas été brisé.

Pour En Savoir Plus

  • Implémentation Explicite De L'Interface
  • nouveau Modificateur. Je ne l'ai pas utilisé dans cette solution simplifiée mais vous pouvez en avoir besoin dans une solution plus compliquée. Pour exemple si vous vouliez créer une interface pour BaseAnimal alors vous auriez besoin de l'utiliser dans votre déclinaison de "excréments de PooType".
  • out modificateur Générique (Covariance) . Encore une fois, je ne l'ai pas utilisé dans cette solution, mais si vous voulez faire quelque chose comme return MyType<Poo> from IAnimal et return MyType<PooType> from BaseAnimal, vous devrez l'utiliser pour pouvoir lancer entre les deux.
14
répondu rob 2012-12-05 20:44:15

Il y a aussi cette option (explicit interface-implementation)

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

Vous perdez la possibilité d'utiliser la classe de base pour implémenter Cat, mais du côté positif, vous gardez le polymorphisme entre Cat et Dog.

Mais je doute que la complexité ajoutée en vaut la peine.

9
répondu Rasmus Faber 2009-06-26 13:21:53

Pourquoi ne pas définir une méthode virtuelle protégée qui crée les "excréments" et garde la propriété publique qui renvoie les "excréments" Non virtuels. Ensuite, les classes dérivées peuvent remplacer le type de retour de la classe de base.

Dans l'exemple suivant, je rends 'excrément 'non virtuel mais je fournis la propriété ExcrementImpl pour permettre aux classes dérivées de fournir le'Poo' approprié. Les types dérivés peuvent alors remplacer le type de retour de 'excrément' en masquant la classe de base application.

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; } }
}
}
4
répondu Hasani Blackwell 2009-10-01 12:59:32

Corrigez - moi si je me trompe mais n'est pas le point entier du pollymorphisme pour pouvoir retourner RadioActivePoo s'il hérite de Poo, le contrat serait le même que la classe abstraite mais juste retourner RadioActivePoo ()

2
répondu almog.ori 2009-06-26 12:37:10

Essayez ceci:

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(); } 
        }
    }
}
2
répondu Shiraz Bhaiji 2009-06-29 06:25:39

Je pense que j'ai trouvé un moyen qui ne dépend pas des génériques ou des méthodes d'extension, mais plutôt du masquage des méthodes. Il peut briser le polymorphisme, cependant, alors soyez particulièrement prudent si vous héritez de Chat.

J'espère que ce post pourrait encore aider quelqu'un, malgré 8 mois de retard.

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; } }
}
1
répondu Cybis 2010-02-20 02:43:48

POUR INFO. Ceci est implémenté assez facilement dans 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)

Voici un exemple en direct avec lequel vous pouvez jouer: http://www.scalakata.com/50d0d6e7e4b0a825d655e832

1
répondu jedesah 2012-12-18 20:52:28

Cela pourrait aider si RadioactivePoo est dérivé de poo et ensuite utiliser des génériques.

0
répondu bobbyalex 2009-06-26 12:35:14

Je crois que votre réponse s'appelle 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();
    }
}
0
répondu Pierre Grondin 2013-03-27 22:51:12

Vous pouvez simplement utiliser un retour D'une Interface. Dans votre cas, IPoo.

Ceci est préférable à l'utilisation d'un type générique, dans votre cas, car vous utilisez une classe de base de commentaires.

0
répondu David Shaskin 2015-05-28 22:32:09

Eh bien, il est en fait possible de retourner un Type concret qui varie du type de retour hérité (même pour les méthodes statiques), grâce à 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);
    }
}

J'ai implémenté ceci dans un wrapper API que j'ai écrit pour mon entreprise. Si vous envisagez de développer une API, cela pourrait améliorer la convivialité et l'expérience de développement dans certains cas d'utilisation. Néanmoins, l'utilisation de dynamic a un impact sur les performances, alors essayez de l'éviter.

0
répondu wobuntu 2017-05-02 09:14:19