Frage

Sie haben Art und Weise Rückgabetypen in C # außer Kraft zu setzen? Wenn ja, wie, und wenn nicht, warum und was ist ein empfohlene Weg, es zu tun?

Mein Fall ist, dass ich eine Schnittstelle mit einer abstrakten Basisklasse und Nachkommen davon habe. Ich möchte, dies tun (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 natürlich erbt von Poo.

Mein Grund, dies zu wollen, so dass diejenigen, die Cat Objekte verwenden, um die Excrement Eigenschaft nutzen könnten, ohne die Poo in RadioactivePoo zu werfen, während beispielsweise die Cat noch Teil einer Animal Liste sein könnte, wo Benutzer nicht unbedingt darüber im Klaren sein können oder kümmern sich um ihre radioaktiven poo. Ich hoffe, dass Sinn gemacht ...

Soweit ich die Compiler dies zumindest nicht erlaubt zu sehen. Also ich denke, es ist unmöglich. Aber was würden Sie empfehlen, als Lösung für diesen?

War es hilfreich?

Lösung

Was ist eine generische 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> { }

Bearbeiten : Eine neue Lösung, mit Erweiterungsmethoden und Markers-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 Hund und Katze sowohl von Animal erben (wie in den Kommentaren bemerkt, meine erste Lösung hat erhalten nicht das Erbe).
Es ist notwendig, explizit die Klassen mit der Marker-Schnittstelle zu markieren, die schmerzhaft ist, aber vielleicht könnte dies Sie ein paar Ideen ...

ZWEITE EDIT @Svish: Ich habe den Code geändert explitly zu zeigen, dass die Extension-Methode nicht in irgendeiner Weise erzwingt die Tatsache, dass iPooProvider von BaseAnimal erbt. Was meinen Sie mit „noch stark typisierte“?

Andere Tipps

Das nennt man Rückgabetyp Kovarianz und nicht in C # oder .NET im Allgemeinen unterstützt, trotz einiger wünscht .

Was ich tun würde, ist die gleiche Signatur halten aber eine zusätzliche ENSURE Klausel der abgeleiteten Klasse hinzuzufügen, in denen ich sicher, dass dies ein ein RadioActivePoo zurückgibt. Also, kurz gesagt, ich über Design by Contract tun würde, was ich kann nicht über Syntax tun.

Andere bevorzugen href="http://peisker.net/dotnet/covariance.htm" rel="nofollow noreferrer"> gefälschte es stattdessen

Das gleiche gilt für Generika, die href="https://stackoverflow.com/questions/1048884/c-overriding-return-types/1048902#1048902"> andere Antworten vorschlagen. Ich würde sie für einen besseren Grund als nur Rückkehr radioaktiven poo verwenden -. Aber das ist nur meine

Ich weiß, dass es viele Lösungen für dieses Problem ist bereits, aber ich glaube, ich habe mit einem kommen, der die Probleme behebt ich mit den bestehenden Lösungen hatte.

war ich mit den einige der bestehenden Lösungen aus den folgenden Gründen nicht zufrieden:

  • Paolo Tedesco erste Lösung. Katze und Hund keine gemeinsame Basisklasse
  • Paolo Tedescos zweite Lösung:. Es ist ein bisschen kompliziert und schwer zu lesen
  • Daniel Daranas Lösung:. Das funktioniert aber es würde den Code mit einer Menge unnötiger Gießen und Debug.Assert () Aussagen verunstaltet
  • hjb417 Lösungen: Mit dieser Lösung läßt Sie nicht Ihre Logik in einer Basisklasse halten. Die Logik ist ziemlich trivial in diesem Beispiel (Aufruf einen Konstruktor), sondern in einem realen Welt Beispiel wäre es nicht.

Meine Lösung

Diese Lösung sollte alle Probleme überwinden ich beide Generika oben erwähnt durch die Verwendung und Verfahren versteckt.

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, die Sie brauchen nichts in Hund oder eine Katze außer Kraft zu setzen! Hier einige Beispiel-Nutzung:

Cat bruce = new Cat();
IAnimal bruceAsAnimal = bruce as IAnimal;
Console.WriteLine(bruce.Excrement.ToString());
Console.WriteLine(bruceAsAnimal.Excrement.ToString());

Dies wird ausgegeben: „RadioactivePoo“ zweimal die zeigt, dass Polymorphismus nicht gebrochen worden ist,

Weiterführende Literatur

  • explizite Schnittstellenimplementierung
  • neue Modifier . Ich habe es nicht in dieser vereinfachten Lösung verwenden, aber Sie können es in einer komplizierteren Lösung benötigen. Wenn Sie zum Beispiel eine Schnittstelle für BaseAnimal dann würden Sie verwenden müssen, um es in Ihrem decleration von „PooType Exkremente“.
  • schaffen wollte
  • aus Generika Modifier (Kovarianz) . Auch hier habe ich es bei dieser Lösung nicht verwenden, aber wenn Sie so etwas wie Rückkehr MyType<Poo> von IAnimal und Rück MyType<PooType> von BaseAnimal tun wollte, dann würden Sie es verwenden müssen, um zwischen den beiden werfen zu können.

Es gibt auch diese Option (explizite Interface-Implementierung)

public class Cat:Animal
{
  Poo Animal.Excrement { get { return Excrement; } }
  public RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
}

Sie verlieren die Fähigkeit, die Basis-Klasse zu verwenden Cat zu implementieren, aber auf der Plus-Seite, halten Sie den Polymorphismus zwischen Katze und Hund.

Aber ich bezweifle, dass die zusätzliche Komplexität ist es wert.

Warum eine geschützte virtuelle Methode nicht definieren, die das ‚Exkremente‘ erstellt und die öffentliche Eigenschaft behalten, dass das ‚Exkremente‘ nicht virtuellen zurückgibt. Dann abgeleiteten Klassen können den Rückgabetyp der Basisklasse außer Kraft setzen.

Im folgende Beispiel I ‚Exkremente‘ nicht-virtuelle machen, sondern bieten die Eigenschaft ExcrementImpl abgeleiteten Klassen zu ermöglichen, den richtigen ‚Poo‘ zur Verfügung zu stellen. Abgeleiteten Typen können dann den Rückgabetyp ‚Exkremente‘ außer Kraft setzen, indem Sie die Basisklassenimplementierung versteckt.

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

Korrigieren Sie mich, wenn im falschen, aber ist nicht der springende Punkt pollymorphism der Lage sein, RadioActivePoo zurück, wenn es von Poo erbt, würde der Vertrag die gleiche wie die abstrakte Klasse sein, aber nur zurückgeben RadioActivePoo ()

Versuchen Sie folgendes:

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, die auf Generika oder Erweiterungsmethoden, sondern Methode versteckt hängen nicht. Es kann Polymorphismus brechen jedoch sein, so besonders vorsichtig, wenn Sie weiter von Cat erben.

Ich hoffe, dieser Beitrag noch jemand helfen könnte, obwohl sie 8 Monate zu spät.

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

Es könnte helfen, wenn RadioactivePoo von poo abgeleitet und dann mit Generika.

Zu Ihrer Information. Dies wird ganz einfach in Scala umgesetzt werden.

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 anschauliches Beispiel Sie spielen können: http://www.scalakata.com/50d0d6e7e4b0a825d655e832

Ich glaube, deine Antwort Kovarianz genannt wird.

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

Sie könnten nur eine Rückkehr eine Schnittstelle verwenden. In Ihrem Fall iPoo.

Dies ist bevorzugt, um einen generischen Typen verwenden, in Ihrem Fall, weil Sie einen Kommentar Basisklasse verwenden.

Nun, es ist tatsächlich möglich, eine konkrete Art zurückzukehren, die von dem geerbten Rückgabetyp variiert (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 implementiert diese in einem API-Wrapper mich für meine Firma geschrieben. Wenn Sie planen, eine API zu entwickeln, hat das Potenzial, um die Benutzerfreundlichkeit und Entwickler Erfahrung in einigen Anwendungsfällen zu verbessern. Dennoch hat die Verwendung von dynamic einen Einfluss auf die Leistung, so versuchen Sie es zu vermeiden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top