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?
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é"?
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.
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 returnMyType<PooType>
from BaseAnimal, vous devrez l'utiliser pour pouvoir lancer entre les deux.
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.
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; } }
}
}
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 ()
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(); }
}
}
}
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; } }
}
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
Cela pourrait aider si RadioactivePoo est dérivé de poo et ensuite utiliser des génériques.
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();
}
}
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.
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.