Gibt es eine Möglichkeit, Rückgabetypen in C # zu überschreiben? Wenn ja wie und wenn nicht warum und wie wird dies empfohlen?
Mein Fall ist, dass ich eine Schnittstelle mit einer abstrakten Basisklasse und deren Nachkommen habe. Das würde ich 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 dafür ist, dass diejenigen, die Cat
-Objekte verwenden, die Excrement
-Eigenschaft verwenden können, ohne die Poo
in RadioactivePoo
umwandeln zu müssen, während beispielsweise Cat
immer noch Teil einer Animal
-Liste sein könnte, bei der sich die Benutzer nicht unbedingt bewusst sind oder sich um sie kümmern radioaktiver poo Hoffe das hat Sinn gemacht ...
Soweit ich sehen kann, lässt der Compiler dies zumindest nicht zu. Ich denke, das ist unmöglich. Aber was würden Sie als Lösung dafür empfehlen?
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, die Erweiterungsmethoden und eine Markierungsschnittstelle verwendet ...
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 sowohl Hund als auch Katze von Animal (wie in den Kommentaren bemerkt, meine erste Lösung bewahrte die Vererbung nicht).
Es ist notwendig, die Klassen explizit mit der Marker-Schnittstelle zu markieren, was schmerzhaft ist, aber dies könnte Ihnen einige Ideen geben ...
SECOND EDIT @Svish: Ich habe den Code geändert, um explizit zu zeigen, dass die Erweiterungsmethode in keiner Weise die Tatsache erzwingt, dass iPooProvider
von BaseAnimal
erbt. Was meinst du mit "noch stärker typisiert"?
Dies wird als return type covariance bezeichnet und wird in C # oder .NET im Allgemeinen nicht unterstützt, trotz der Wünsche einiger Personen.
Ich würde die gleiche Signatur beibehalten, aber der abgeleiteten Klasse eine zusätzliche ENSURE
-Klausel hinzufügen, in der ich sicher bin, dass diese eine RadioActivePoo
zurückgibt. Kurz gesagt, ich würde über Design durch Vertrag tun, was ich nicht über Syntax tun kann.
Andere ziehen es vor, stattdessen fake _. 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 das erreichen, obwohl dies kein Mechanismus für die Kompilierung ist.
Dasselbe gilt für Generika, die andere Antworten vorschlagen. Ich würde sie aus einem besseren Grund verwenden, als nur radioaktives Poo zurückzugeben - aber das ist nur ich.
Ich weiß, dass es bereits viele Lösungen für dieses Problem gibt, aber ich denke, ich habe eine Lösung gefunden, die die Probleme löst, die ich mit den vorhandenen Lösungen hatte.
Ich war aus folgenden Gründen mit den vorhandenen Lösungen nicht zufrieden:
Meine Lösung
Diese Lösung sollte alle oben genannten Probleme lösen, indem sowohl Generics als auch Method Hiding 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 bei Dog OR Cat nichts außer Kraft setzen! Wie cool ist das? 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());
Daraufhin wird zweimal "RadioactivePoo" ausgegeben, was zeigt, dass der Polymorphismus nicht gebrochen wurde.
Weiterführende Literatur
MyType<Poo>
von IAnimal und MyType<PooType>
von BaseAnimal zurückgeben wollten, müssten Sie es verwenden, um zwischen den beiden zu wechseln.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 zu verwenden, um Cat zu implementieren, aber auf der positiven Seite behalten Sie den Polymorphismus zwischen Katze und Hund.
Ich bezweifle jedoch, dass sich die zusätzliche Komplexität lohnt.
Warum nicht eine geschützte virtuelle Methode definieren, die den 'Excrement' erstellt, und die öffentliche Eigenschaft, die den 'Excrement' zurückgibt, nicht virtuell hält. Abgeleitete Klassen können dann den Rückgabetyp der Basisklasse überschreiben.
Im folgenden Beispiel mache ich 'Excrement' nicht-virtuell, stelle jedoch die Eigenschaft ExcrementImpl zur Verfügung, damit abgeleitete Klassen das richtige 'Poo' bereitstellen können. Abgeleitete Typen können dann den Rückgabetyp 'Excrement' überschreiben, indem Sie die Implementierung der Basisklasse ausblenden.
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; } }
}
}
Korrigieren Sie mich, wenn ich falsch bin, aber nicht der ganze 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 es wird nur RadioActivePoo () zurückgegeben.
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(); }
}
}
}
Ich glaube, ich habe einen Weg gefunden, der nicht von Generika oder Erweiterungsmethoden abhängt, sondern eher von Methodenverstecken. Es kann jedoch den Polymorphismus brechen, seien Sie also besonders vorsichtig, wenn Sie von Cat weiter erben.
Ich hoffe, dass dieser Beitrag immer noch jemandem helfen kann, obwohl er 8 Monate zu spät kommt.
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; } }
}
Zu Ihrer Information. Dies ist in Scala recht einfach umzusetzen.
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
Es kann hilfreich sein, wenn RadioactivePoo von poo abgeleitet ist und dann Generika verwendet.
Sie können einfach ein Interface zurückgeben. In Ihrem Fall IPoo.
Dies ist der Verwendung eines generischen Typs in Ihrem Fall vorzuziehen, da Sie eine Kommentarbasisklasse verwenden.
Nun, es ist tatsächlich möglich, einen konkreten Typ zurückzugeben, der sich vom geerbten Rückgabetyp unterscheidet (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 planen, eine API zu entwickeln, kann dies in einigen Anwendungsfällen die Benutzerfreundlichkeit und die Entwicklungserfahrung verbessern. Die Verwendung von dynamic
wirkt sich jedoch auf die Leistung aus. Versuchen Sie daher, dies zu vermeiden.
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();
}
}