web-dev-qa-db-fra.com

C #: Ignorer les types de retour

Existe-t-il un moyen de remplacer les types de retour en C #? Si oui comment, et si non pourquoi et quelle est la méthode recommandée pour le faire?

Mon cas est que j'ai une interface avec une classe de base abstraite et ses descendants. J'aimerais faire ceci (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.

Ce que je veux, c'est que ceux qui utilisent des objets Cat puissent utiliser la propriété Excrement sans avoir à convertir Poo en RadioactivePoo alors que, par exemple, Cat pourrait toujours faire partie d'une liste Animal où les utilisateurs ne seraient pas nécessairement conscients de leur caca radioactif. J'espère que ça a du sens ...

Autant que je sache, le compilateur ne le permet pas du moins. Donc je suppose que c'est impossible. Mais que recommanderiez-vous comme solution à cela? 

71
Svish

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, Dog et Cat héritent tous les deux d'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 marqueur, ce qui est douloureux, mais peut-être que cela pourrait vous donner quelques idées ...

SECOND EDIT @Svish: J'ai modifié le code pour indiquer explicitement que la méthode d'extension n'impose en aucune manière le fait que iPooProvider hérite de BaseAnimal. Qu'entendez-vous par "encore plus fortement typé"?

46
Paolo Tedesco

Cela s'appelle return type covariance et n'est pas pris en charge en C # ou .NET en général, malgré le souhaits de certaines personnes.

Ce que je ferais, c’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 retourne un RadioActivePoo. En bref, je ferais par contrat, ce que je ne pouvais pas faire par syntaxe.

D'autres préfèrent le faux à la place. C'est bon, je suppose, mais j'ai tendance à économiser les lignes de code "infrastructure". Si la sémantique du code est suffisamment claire, je suis contente, et la conception par contrat me permet d'y parvenir, bien que ce ne soit pas un mécanisme de compilation.

Même chose pour les génériques, ce que autres réponses suggère. Je les utiliserais pour une meilleure raison que le simple retour du caca radioactif - mais ce n'est que moi.

28
Daniel Daranas

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

Certaines des solutions existantes ne me convenaient pas pour les raisons suivantes:

  • Première solution de Paolo Tedesco: Cat et Dog n'ont pas de classe de base commune.
  • Deuxième solution de Paolo Tedesco: C'est un peu compliqué et difficile à lire.
  • La solution de Daniel Daranas: Cela fonctionne mais cela encombrerait votre code avec beaucoup de transtypages inutiles et d'instructions Debug.Assert ().
  • Les solutions de hjb417: Cette solution ne vous permet pas de conserver votre logique dans une classe de base. La logique est assez triviale dans cet exemple (appeler un constructeur), mais pas dans un exemple réel.

Ma solution

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

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 ne devez rien remplacer dans Dog OR Cat! À quel point cela est cool? Voici quelques exemples d'utilisation:

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

Cela produira: "RadioactivePoo" deux fois, ce qui montre que le polymorphisme n'a pas été rompu.

Lectures supplémentaires

  • Implémentation d'interface explicite
  • nouveau modificateur . Je ne l'ai pas utilisé dans cette solution simplifiée, mais vous en aurez peut-être besoin dans une solution plus complexe. Par exemple, si vous souhaitez créer une interface pour BaseAnimal, vous devez l’utiliser dans votre décélération de "PooType Excrement".
  • out Modificateur générique (Covariance) . Encore une fois, je ne l’utilisais pas dans cette solution, mais si vous vouliez faire quelque chose comme return MyType<Poo> depuis IAnimal et MyType<PooType> depuis BaseAnimal, il vous faudrait l’utiliser pour pouvoir transtyper les deux.
15
rob

Il y a aussi cette option (implémentation d'interface explicite)

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 conservez le polymorphisme entre Cat et Dog.

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

9
Rasmus Faber

Pourquoi ne pas définir une méthode virtuelle protégée qui crée «Excrement» et conserver la propriété publique qui renvoie «Excrement» non virtuel. Les classes dérivées peuvent alors remplacer le type de retour de la classe de base.

Dans l'exemple suivant, je mets 'Excrement' 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 ensuite remplacer le type de retour «Excrément» en masquant l'implémentation de la classe de 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; } }
}
}
4
Hasani Blackwell

Corrigez-moi si je me trompe, mais si ce n’est pas l’intérêt du pollymorphisme de pouvoir renvoyer RadioActivePoo s’il hérite de Poo, le contrat serait identique à la classe abstraite mais renverrait simplement RadioActivePoo ()

2
almog.ori

Essaye ça:

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
Shiraz Bhaiji

Je pense avoir trouvé un moyen qui ne dépend pas des méthodes génériques ou d'extension, mais plutôt du masquage de méthode. Cependant, il peut rompre le polymorphisme. Soyez donc particulièrement prudent si vous héritez davantage de Cat. 

J'espère que ce poste pourra toujours aider quelqu'un malgré son retard de 8 mois.

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
Cybis

FYI. 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 concret avec lequel vous pouvez jouer: http://www.scalakata.com/50d0d6e7e4b0a825d655e832

0
jedesah

Cela pourrait aider si RadioactivePoo est dérivé du caca et utilise ensuite des génériques.

0
bobbyalex

Vous pouvez simplement utiliser un retour une interface. Dans ton 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
David Shaskin

Eh bien, il est en fait possible de retourner un type concret qui diffère 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);
    }
}

Je l'ai implémenté dans un wrapper d'API que j'ai écrit pour mon entreprise. Si vous envisagez de développer une API, cela peut potentiellement 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, essayez donc de l'éviter.

0
wobuntu

Je crois que ta 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
Pierre Grondin