wake-up-neo.com

C #: Rückgabetypen überschreiben

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? 

71
Svish

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"?

46
Paolo Tedesco

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.

28
Daniel Daranas

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:

  • Paolo Tedescos erste Lösung: Katze und Hund haben keine gemeinsame Basisklasse.
  • Paolo Tedescos zweite Lösung: Es ist etwas kompliziert und schwer zu lesen.
  • Daniel Daranas Lösung: Das funktioniert, aber es würde Ihren Code mit unnötigen Castings und Debug.Assert () -Anweisungen verstopfen.
  • hjb417-Lösungen: Mit dieser Lösung können Sie Ihre Logik nicht in einer Basisklasse halten. Die Logik ist in diesem Beispiel ziemlich trivial (Aufruf eines Konstruktors), aber in einem realen Beispiel wäre dies nicht der Fall.

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

  • Explizite Schnittstellenimplementierung
  • neuer Modifikator . Ich habe es in dieser vereinfachten Lösung nicht verwendet, aber Sie benötigen es vielleicht in einer komplizierteren Lösung. Wenn Sie beispielsweise ein Interface für BaseAnimal erstellen möchten, müssen Sie es in Ihrer Deklaration von "PooType Excrement" verwenden.
  • out Generic Modifier (Kovarianz) . Ich habe es auch nicht in dieser Lösung verwendet, aber wenn Sie etwas wie return_MyType<Poo> von IAnimal und MyType<PooType> von BaseAnimal zurückgeben wollten, müssten Sie es verwenden, um zwischen den beiden zu wechseln.
15
rob

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.

9
Rasmus Faber

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; } }
}
}
4

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.

2
almog.ori

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(); } 
        }
    }
}
2
Shiraz Bhaiji

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; } }
}
1
Cybis

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

0
jedesah

Es kann hilfreich sein, wenn RadioactivePoo von poo abgeleitet ist und dann Generika verwendet.

0
bobbyalex

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. 

0
David Shaskin

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.

0
wobuntu

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();
    }
}
0
Pierre Grondin