Gibt es eine Möglichkeit, Rückgabetypen in C # zu überschreiben? Wenn ja wie und wenn nicht warum und was ist eine empfohlene Vorgehensweise?
Mein Fall ist, dass ich eine Schnittstelle mit einer abstrakten Basisklasse und deren Nachkommen habe. Ich würde das gerne machen (ok nicht wirklich, aber als Beispiel!):
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
erbt natürlich von Poo
.
Mein Grund, dies zu wollen, ist, dass diejenigen, die Cat
Objekte verwenden, die Excrement
Eigenschaft nutzen können, ohne sie einwerfen Poo
zu müssen, RadioactivePoo
während sie beispielsweise Cat
immer noch Teil einer Animal
Liste sein können, in der Benutzer möglicherweise nicht unbedingt über ihren radioaktiven Kot informiert sind oder sich darum kümmern. Hoffe das hat Sinn gemacht ...
Soweit ich sehen kann, lässt der Compiler dies zumindest nicht zu. Ich denke es ist unmöglich. Aber was würden Sie als Lösung dafür empfehlen?
quelle
Antworten:
Ich weiß, dass es bereits viele Lösungen für dieses Problem gibt, aber ich denke, ich habe eine gefunden, die die Probleme behebt, die ich mit den vorhandenen Lösungen hatte.
Ich war aus folgenden Gründen mit einigen der vorhandenen Lösungen nicht zufrieden:
Meine Lösung
Diese Lösung sollte alle oben genannten Probleme lösen, indem sowohl Generika als auch Methoden verwendet werden.
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> { }
Mit dieser Lösung müssen Sie nichts in Dog OR Cat überschreiben! Hier einige Beispiele für die Verwendung:
Cat bruce = new Cat(); IAnimal bruceAsAnimal = bruce as IAnimal; Console.WriteLine(bruce.Excrement.ToString()); Console.WriteLine(bruceAsAnimal.Excrement.ToString());
Dies wird zweimal "RadioactivePoo" ausgeben, was zeigt, dass der Polymorphismus nicht gebrochen wurde.
Weiterführende Literatur
MyType<Poo>
von IAnimal und die RückkehrMyType<PooType>
von BaseAnimal durchführen möchten, müssen Sie es verwenden, um zwischen den beiden wechseln zu können.quelle
Was ist mit einer generischen Basisklasse?
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 : Eine neue Lösung mit Erweiterungsmethoden und einer Marker-Schnittstelle ...
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(); } }
Auf diese Weise erben Hund und Katze beide von Animal (wie in den Kommentaren erwähnt, hat meine erste Lösung das Erbe nicht bewahrt).
Es ist notwendig, die Klassen explizit mit der Marker-Oberfläche zu markieren, was schmerzhaft ist, aber vielleicht könnte dies Ihnen einige Ideen geben ...
ZWEITE BEARBEITUNG @Svish: Ich habe den Code geändert, um explizit zu zeigen, dass die Erweiterungsmethode die Tatsache, von der
iPooProvider
erbt , in keiner Weise erzwingtBaseAnimal
. Was meinst du mit "noch stärker typisiert"?quelle
Dies wird als Kovarianz vom Rückgabetyp bezeichnet und wird in C # oder .NET im Allgemeinen trotz der Wünsche einiger Leute nicht unterstützt .
Was ich tun würde, ist die gleiche Signatur
ENSURE
beizubehalten, aber der abgeleiteten Klasse eine zusätzliche Klausel hinzuzufügen, in der ich sicherstelle, dass diese eine zurückgibtRadioActivePoo
. Kurz gesagt, ich würde über Design by Contract tun, was ich nicht über Syntax tun kann.Andere fälschen es lieber. Es ist in Ordnung, denke ich, aber ich neige dazu, "Infrastruktur" -Codezeilen zu sparen. Wenn die Semantik des Codes klar genug ist, bin ich glücklich, und durch vertragliches Design kann ich dies erreichen, obwohl es sich nicht um einen Mechanismus zur Kompilierungszeit handelt.
Gleiches gilt für Generika, die andere Antworten nahe legen. Ich würde sie aus einem besseren Grund verwenden, als nur radioaktiven Kot zurückzugeben - aber das bin nur ich.
quelle
Es gibt auch diese Option (explizite Schnittstellenimplementierung)
public class Cat:Animal { Poo Animal.Excrement { get { return Excrement; } } public RadioactivePoo Excrement { get { return new RadioactivePoo(); } } }
Sie verlieren die Fähigkeit, die Basisklasse zum Implementieren von Cat zu verwenden, aber auf der positiven Seite behalten Sie den Polymorphismus zwischen Cat und Dog bei.
Aber ich bezweifle, dass sich die zusätzliche Komplexität lohnt.
quelle
Definieren Sie eine geschützte virtuelle Methode, die das 'Exkrement' erstellt, und behalten Sie die öffentliche Eigenschaft bei, die das 'Exkrement' nicht virtuell zurückgibt. Dann können abgeleitete Klassen den Rückgabetyp der Basisklasse überschreiben.
Im folgenden Beispiel mache ich 'Excrement' nicht virtuell, stelle aber die Eigenschaft ExcrementImpl bereit, damit abgeleitete Klassen das richtige 'Poo' bereitstellen können. Abgeleitete Typen können dann den Rückgabetyp von 'Excrement' überschreiben, indem die Implementierung der Basisklasse ausgeblendet wird.
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; } } } }
quelle
Korrigieren Sie mich, wenn ich falsch liege, aber nicht der springende Punkt des Pollymorphismus ist, um RadioActivePoo zurückgeben zu können, wenn es von Poo erbt. Der Vertrag wäre der gleiche wie die abstrakte Klasse, aber geben Sie einfach RadioActivePoo () zurück.
quelle
Versuche dies:
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(); } } } }
quelle
Ich glaube, ich habe einen Weg gefunden, der nicht von Generika oder Erweiterungsmethoden abhängt, sondern von Methoden, die sich verstecken. Es kann jedoch den Polymorphismus brechen. Seien Sie also besonders vorsichtig, wenn Sie weiter von Cat erben.
Ich hoffe, dieser Beitrag konnte noch jemandem helfen, obwohl er 8 Monate zu spät kam.
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; } } }
quelle
Es kann hilfreich sein, wenn RadioactivePoo von poo abgeleitet ist und dann Generika verwendet.
quelle
Zu Ihrer Information. Dies ist in Scala recht einfach zu implementieren.
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)
Hier ist ein Live-Beispiel, mit dem Sie spielen können: http://www.scalakata.com/50d0d6e7e4b0a825d655e832
quelle
Ich glaube, Ihre Antwort heißt Kovarianz.
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(); } }
quelle
Sie können einfach eine Schnittstelle zurückgeben. In Ihrem Fall IPoo.
Dies ist in Ihrem Fall der Verwendung eines generischen Typs vorzuziehen, da Sie eine Kommentarbasisklasse verwenden.
quelle
Im Folgenden werden einige der besten Aspekte mehrerer anderer Antworten sowie eine Technik kombiniert, um den Schlüsselaspekt
Cat
einerExcrement
Eigenschaft des erforderlichenRadioactivePoo
Typs zu ermöglichen. Sie können dies jedoch nur dann zurückgeben,Poo
wenn wir nur wissen, dass wir eineAnimalBase
eher als haben speziell aCat
.Aufrufer müssen weder Generika verwenden, obwohl sie in den Implementierungen vorhanden sind, noch eine Funktion mit einem anderen Namen aufrufen, um etwas Besonderes zu erhalten
Poo
.Die Zwischenklasse
AnimalWithSpecialisations
dient nur zum Versiegeln derExcrement
Eigenschaft und verbindet sie über eine nicht öffentlicheSpecialPoo
Eigenschaft mit der abgeleiteten Klasse,AnimalWithSpecialPoo<TPoo>
die eineExcrement
Eigenschaft eines abgeleiteten Rückgabetyps hat.Wenn
Cat
es das einzige TierPoo
ist, das in irgendeiner Weise etwas Besonderes ist, oder wenn wir nicht möchten, dass der TypExcrement
das Hauptdefinitionsmerkmal von a istCat
, könnte die generische Zwischenklasse in der Hierarchie übersprungen werden, sodass sieCat
direkt von abgeleitet wirdAnimalWithSpecialisations
, aber wenn vorhanden Es gibt mehrere verschiedene Tiere, deren Hauptmerkmal darin besteht, dass siePoo
in irgendeiner Weise etwas Besonderes sind. Die Aufteilung der "Kesselplatte" in Zwischenklassen hilft dabei, die Tiere zu haltenCat
Klasse selbst ziemlich sauber , wenn auch auf Kosten zusätzlicher virtueller Funktionsaufrufe.Der Beispielcode zeigt, dass die meisten erwarteten Vorgänge "wie erwartet" funktionieren.
public interface IExcretePoo<out TPoo> where TPoo : Poo { TPoo Excrement { get; } } public class Poo { } public class RadioactivePoo : Poo { } public class AnimalBase : IExcretePoo<Poo> { public virtual Poo Excrement { get { return new Poo(); } } } public class Dog : AnimalBase { // No override, just return normal poo like normal animal } public abstract class AnimalWithSpecialisations : AnimalBase { // this class connects AnimalBase to AnimalWithSpecialPoo<TPoo> public sealed override Poo Excrement { get { return SpecialPoo; } } // if not overridden, our "special" poo turns out just to be normal animal poo... protected virtual Poo SpecialPoo { get { return base.Excrement; } } } public abstract class AnimalWithSpecialPoo<TPoo> : AnimalWithSpecialisations, IExcretePoo<TPoo> where TPoo : Poo { sealed protected override Poo SpecialPoo { get { return Excrement; } } public new abstract TPoo Excrement { get; } } public class Cat : AnimalWithSpecialPoo<RadioactivePoo> { public override RadioactivePoo 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.Excrement; AnimalBase animal = cat; Poo animalPoo = catPoo; animalPoo = animal.Excrement; AnimalWithSpecialPoo<RadioactivePoo> radioactivePooingAnimal = cat; RadioactivePoo radioactivePoo = radioactivePooingAnimal.Excrement; IExcretePoo<Poo> pooExcreter = cat; // through this interface we don't know the Poo was radioactive. IExcretePoo<RadioactivePoo> radioactivePooExcreter = cat; // through this interface we do. // we can replace these with the dog equivalents: animal = dog; animalPoo = dogPoo; pooExcreter = dog; // but we can't do: // radioactivePooExcreter = dog; // radioactivePooingAnimal = dog; // radioactivePoo = dogPoo; }
quelle
C # 9 gibt uns kovariante Override-Rückgabetypen. Grundsätzlich gilt: Was Sie wollen, funktioniert einfach .
quelle
Nun, es ist tatsächlich möglich, einen konkreten Typ zurückzugeben, der vom geerbten Rückgabetyp abweicht (auch für statische Methoden), dank
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); } }
Ich habe dies in einem API-Wrapper implementiert, den ich für mein Unternehmen geschrieben habe. Wenn Sie vorhaben, eine API zu entwickeln, kann dies in einigen Anwendungsfällen die Benutzerfreundlichkeit und die Entwicklererfahrung verbessern. Die Verwendung von
dynamic
wirkt sich jedoch auf die Leistung aus. Versuchen Sie daher, dies zu vermeiden.quelle