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?
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é"?
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.
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:
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
MyType<Poo>
depuis IAnimal et MyType<PooType>
depuis BaseAnimal, il vous faudrait l’utiliser pour pouvoir transtyper 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é positif, vous conservez le polymorphisme entre Cat et Dog.
Mais je doute que la complexité ajoutée en vaille la peine.
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; } }
}
}
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 ()
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(); }
}
}
}
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; } }
}
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
Cela pourrait aider si RadioactivePoo est dérivé du caca et utilise ensuite des génériques.
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.
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.
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();
}
}