88 votes

C#: le remplacement des types de retour

Est-il possible de supprimer des types de retour en C#? Si oui, comment, et si non, pourquoi et ce qui est une bonne façon de faire?

Mon cas est que j'ai une interface avec une classe de base abstraite et descendants. Je voudrais faire ceci (ok, pas vraiment, mais comme un 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 de cours en hérite Poo.

Ma raison de vouloir ceci est fait de sorte que ceux qui utilisent Cat objets pourraient utiliser l' Excrement de la propriété sans avoir à lancer l' Poo en RadioactivePoo alors que par exemple l' Cat pourrait encore faire partie d'un Animal liste où les utilisateurs ne peuvent pas nécessairement être au courant ou se soucient de leur radioactifs poo. L'espoir qui fait sens...

Aussi loin que je peux voir le compilateur ne permet pas ceci au moins. Donc je suppose que c'est impossible. Mais que recommanderiez-vous comme solution?

50voto

Paolo Tedesco Points 22442

À propos d'une classe générique de base?

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, en utilisant des méthodes d'extension et un marqueur de l'interface...

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 à la fois hériter de l'Animal (comme l'a remarqué dans les commentaires, ma première solution n'est pas de préserver l'héritage).
Il est nécessaire de marquer explicitement les classes avec le marqueur de l'interface, ce qui est douloureux, mais peut-être que cela pourrait vous donner quelques idées...

DEUXIÈME EDIT @Svish: j'ai modifié le code pour afficher explitly que l'extension de la méthode est de ne pas appliquer en aucune façon le fait qu' iPooProvider hérite BaseAnimal. Qu'entendez-vous par "encore plus fortement typées"?

35voto

rob Points 3029

Je sais qu'il y a beaucoup de solutions pour ce problème déjà, mais je pense que je suis venu avec un qui résout les problèmes que j'ai eu avec les solutions existantes.

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

  • Paolo Tedesco première solution: le Chat et le Chien n'ont pas une classe de base commune.
  • Paolo Tedesco deuxième solution: C'est un peu compliqué et difficile à lire.
  • Daniel Daranas la solution: Cela fonctionne, mais il serait encombrer votre code avec beaucoup d'inutiles casting et de Débogage.Assert() consolidés.
  • hjb417 solutions: Cette solution ne permet pas de garder votre logique dans une classe de base. La logique est assez trivial dans cet exemple (appel d'un constructeur), mais dans un monde réel exemple, il ne serait pas.

Ma Solution

Cette solution devrait permettre de surmonter tous les problèmes que j'ai mentionnés ci-dessus en utilisant à la fois génériques et de la 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 modifier quoi que ce soit dans Chien OU un Chat! C'est pas cool ça? Voici quelques exemples d'utilisation:

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

Cette sortie: "RadioactivePoo" deux fois, ce qui montre que le polymorphisme n'a pas été brisé.

Lectures Complémentaires

  • Implémentation D'Interface Explicite
  • nouveau Modificateur. Je n'avais pas l'utiliser dans cette solution simplifiée, mais vous pouvez avoir besoin dans une plus compliqué solution. Par exemple, si vous vouliez créer une interface pour BaseAnimal ensuite, vous devez utiliser dans votre decleration de "PooType Excréments".
  • hors Générique Modificateur (Covariance). Encore une fois je n'ai pas l'utiliser dans cette solution, mais si vous vouliez faire quelque chose comme retour MyType<Poo> de IAnimal et retour MyType<PooType> de BaseAnimal alors vous devez l'utiliser pour être en mesure de lancer entre les deux.

33voto

Daniel Daranas Points 15123

Ceci est appelé le type de retour de la covariance et n'est pas pris en charge en C# ou .NET en général, en dépit de certaines personnes, souhaite.

Ce que je voudrais faire est de garder la même signature, mais ajouter un ENSURE de la clause de la classe dérivée dans laquelle je m'assurer que celui-ci renvoie un RadioActivePoo. Donc, en bref, je le ferais via la conception par contrat, ce que je ne peux pas le faire via la syntaxe.

D'autres préfèrent faux à la place. C'est ok, je suppose, mais j'ai tendance à économiser sur les "infrastructures" de lignes de code. Si la sémantique du code sont assez claires, je suis heureux, et de la conception par contrat me permet de réaliser que, bien qu'il n'est pas un moment de la compilation mécanisme.

La même chose pour les génériques, d'autres réponses suggèrent. Je voudrais les utiliser pour une autre raison que celle juste retour radioactifs poo - mais c'est juste moi.

10voto

Rasmus Faber Points 24195

Il y a aussi cette option (interface explicite-de la mise en œuvre)

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 mettre en œuvre Chat, mais sur la plus-côté, vous gardez le polymorphisme entre Chat et Chien.

Mais je doute de la complexité supplémentaire en vaut la peine.

4voto

hjb417 Points 1186

Pourquoi ne pas définir une méthode virtuelle protégée, qui crée les "Excréments" et garder la propriété publique qui renvoie l '"Excréments" non virtuel. Puis les classes dérivées peuvent substituer le type de retour de la classe de base.

Dans l'exemple suivant, je fais des crottes de " non-virtuel, mais de fournir la propriété ExcrementImpl pour permettre aux classes dérivées de fournir la bonne 'Caca'. Les types dérivés peut ensuite remplacer le type de retour de la "Excréments" par le masquage de la classe de base de la mise en œuvre.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X