C #: Remplacement des types de retour

Est-il possible de remplacer les types de retour en C #? Si oui, comment et si non, pourquoi et comment le faire?

Mon cas est que j’ai une interface avec une classe de base abstraite et ses descendants. Je voudrais le faire (ok pas vraiment, mais par 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 hérite bien sûr de Poo .

Ma raison de le vouloir est que ceux qui utilisent des objects Cat puissent utiliser la propriété Excrement sans avoir à convertir le Poo en RadioactivePoo alors que le Cat pourrait toujours faire partie d’une liste d’ Animal où les utilisateurs ne seraient pas nécessairement conscients ou soucieux de leur caca radioactif. J’espère que c’était logique …

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 à cela?

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

 public class Poo { } public class RadioactivePoo : Poo { } public class BaseAnimal where PooType : Poo, new() { PooType Excrement { get { return new PooType(); } } } public class Dog : BaseAnimal { } public class Cat : BaseAnimal { } 

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 { } // Extension method to get the correct type of excrement public static class IPooProviderExtension { public static PooType StronglyTypedExcrement( this IPooProvider 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 { } public class Cat : BaseAnimal, IPooProvider { public override Poo Excrement { get { return new RadioactivePoo(); } } } class Program { static void Main(ssortingng[] args) { Dog dog = new Dog(); Poo dogPoo = dog.Excrement; Cat cat = new Cat(); RadioactivePoo catPoo = cat.StronglyTypedExcrement(); } } 

De cette façon, Dog et Cat ont tous deux hérité d’Animal (comme le font remarquer les commentaires, ma première solution n’a pas préservé l’inheritance).
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 montrer explicitement que la méthode d’extension n’impose en aucune façon le fait iPooProvider hérite de BaseAnimal . Que voulez-vous dire par “encore plus fortement typé”?

Cela s’appelle covariance de type retour et n’est pas supporté en général dans C # ou .NET, malgré les souhaits de certaines personnes.

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

D’autres préfèrent le truquer à 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 content, et la conception par contrat me permet d’y parvenir, même s’il ne s’agit pas d’un mécanisme de compilation.

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

Je sais qu’il existe déjà de nombreuses solutions à ce problème, mais je pense en avoir une qui corrige les problèmes rencontrés avec les solutions existantes.

Je n’étais pas satisfait des solutions existantes pour les raisons suivantes:

  • La première solution de Paolo Tedesco: Cat et Dog n’ont pas de classe de base commune.
  • La 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 de déclarations 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 sortingviale dans cet exemple (en appelant un constructeur) mais dans un exemple réel, ce ne serait pas le cas.

Ma solution

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

 public class Poo { } public class RadioactivePoo : Poo { } interface IAnimal { Poo Excrement { get; } } public class BaseAnimal : IAnimal where PooType : Poo, new() { Poo IAnimal.Excrement { get { return (Poo)this.Excrement; } } public PooType Excrement { get { return new PooType(); } } } public class Dog : BaseAnimal { } public class Cat : BaseAnimal { } 

Avec cette solution, vous n’avez pas besoin de remplacer quoi que ce soit 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.ToSsortingng()); Console.WriteLine(bruceAsAnimal.Excrement.ToSsortingng()); 

Cela va sortir: “RadioactivePoo” deux fois, ce qui montre que le polymorphism n’a pas été brisé.

Lectures complémentaires

  • Mise en œuvre d’interface explicite
  • nouveau modificateur Je ne l’ai pas utilisé dans cette solution simplifiée, mais vous pourriez en avoir besoin dans une solution plus complexe. Par exemple, si vous souhaitez créer une interface pour BaseAnimal, vous devez l’utiliser dans votre décomposition de “PooType Excrement”.
  • Modificateur générique (covariance) . Encore une fois, je ne l’ai pas utilisé dans cette solution mais si vous vouliez faire quelque chose comme retourner MyType depuis IAnimal et retourner MyType depuis BaseAnimal, vous devriez l’utiliser pour pouvoir passer entre les deux.

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é des avantages, vous conservez le polymorphism entre Cat et Dog.

Mais je doute que la complexité supplémentaire en vaille la peine.

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

Dans l’exemple suivant, je ne mets pas «Excrement» en ligne, 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 ‘Excrement’ 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; } } } } 

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

Essaye ça:

 namespace ClassLibrary1 { public interface Animal { Poo Excrement { get; } } public class Poo { } public class RadioactivePoo { } public class AnimalBase { public virtual T Excrement { get { return default(T); } } } public class Dog : AnimalBase { // No override, just return normal poo like normal animal } public class Cat : AnimalBase { public override RadioactivePoo Excrement { get { return new RadioactivePoo(); } } } } 

Je pense avoir trouvé un moyen qui ne dépend pas des génériques ou des méthodes d’extension, mais plutôt de la méthode. Il peut cependant casser le polymorphism, donc soyez particulièrement prudent si vous héritez de Cat.

J’espère que ce poste pourrait encore 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; } } } 

FYI. Ceci est implémenté assez facilement à Scala.

 trait Path trait Resource { def copyTo(p: Path): Resource } class File extends Resource { override def copyTo(p: Path): File = new File override def toSsortingng = "File" } class Directory extends Resource { override def copyTo(p: Path): Directory = new Directory override def toSsortingng = "Directory" } val test: Resource = new Directory() test.copyTo(null) 

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

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

Je crois que votre réponse s’appelle covariance.

 class Program { public class Poo { public virtual ssortingng Name { get{ return "Poo"; } } } public class RadioactivePoo : Poo { public override ssortingng Name { get { return "RadioactivePoo"; } } public ssortingng DecayPeriod { get { return "Long time"; } } } public interface IAnimal where T : Poo { T Excrement { get; } } public class Animal:IAnimal where T : Poo { public T Excrement { get { return _excrement ?? (_excrement = (T) Activator.CreateInstance(typeof (T), new object[] {})); } } private T _excrement; } public class Dog : Animal{} public class Cat : Animal{} static void Main(ssortingng[] args) { var dog = new Dog(); var cat = new Cat(); IAnimal animal1 = dog; IAnimal 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 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(); } } 

Vous pouvez simplement utiliser une interface de retour. 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 commentaire.

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

Je l’ai implémenté dans une enveloppe API que j’ai écrite 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 la dynamic a un impact sur les performances, alors essayez de l’éviter.