2008-09-05 3 views
8

Ich habe einige Vererbungsfragen, da ich eine Gruppe von zusammenhängenden abstrakten Klassen habe, die alle zusammen überschrieben werden müssen, um eine Client-Implementierung zu erstellen. Im Idealfall würde Ich mag so etwas wie die folgenden Funktionen ausführen:Warum funktioniert die Vererbung nicht so, wie ich denke, dass sie funktionieren sollte?

abstract class Animal 
{ 
    public Leg GetLeg() {...} 
} 

abstract class Leg { } 

class Dog : Animal 
{ 
    public override DogLeg Leg() {...} 
} 

class DogLeg : Leg { } 

Dies jemand erlauben würde, die Dog-Klasse automatisch Doglegs und alle bekommt die Tier-Klasse Beine zu bekommen. Das Problem besteht darin, dass die überschriebene Funktion den gleichen Typ wie die Basisklasse haben muss, damit diese nicht kompiliert wird. Ich verstehe nicht, warum das nicht so sein sollte, denn DogLeg ist implizit auf Leg legal. Ich weiß, dass es viele Möglichkeiten gibt, aber ich bin neugierig, warum dies in C# nicht möglich ist.

BEARBEITEN: Ich habe dies etwas geändert, da ich tatsächlich Eigenschaften anstelle von Funktionen in meinem Code verwende.

EDIT: Habe ich es zu Funktionen zurück, weil die Antwort auf diese Situation (Kovarianz auf dem Wert Parameter einer Set-Eigenschaft Funktion soll nicht Arbeit) gilt nur. Entschuldigung für die Schwankungen! Mir ist klar, dass viele Antworten irrelevant erscheinen.

Antwort

15

Die kurze Antwort ist, dass GetLeg in seinem Rückgabetyp invariant ist. Die lange Antwort finden Sie hier: Covariance and contravariance

Ich möchte hinzufügen, dass während Vererbung in der Regel das erste Abstraktionswerkzeug ist, das die meisten Entwickler aus ihrer Toolbox ziehen, ist es fast immer möglich, stattdessen Komposition zu verwenden.Komposition ist etwas mehr Arbeit für den API-Entwickler, macht aber die API für ihre Konsumenten nützlicher.

+0

Ihre Aussage zur Abweichung macht keinen Sinn. In einem Subtyp: Covariance = der verwandte Typ wird untergeteilt & Contravariance = verwandter Typ wird supertypisiert. Diese Frage stellt einen Kovarianten-Rückgabetyp (Untertyp in einem Subtyp) dar, wenn C# nur einen invarianten (gleichen) Rückgabetyp zulässt. –

+0

Die Artikel, die Sie verlinkt haben, handeln von generischen Varianztypen. Die Frage betrifft die Kovarianz vom Rückgabetyp. Ich stelle in diesen Artikeln ausdrücklich fest, dass ich * nicht * über Rückgabetyp-Kovarianz spreche. –

+0

Eric Lippert: Aber diese Dinge sind untrennbar miteinander verbunden. Siehe hier: http://apocalisp.wordpress.com/2009/08/27/hostility-toward-subtyping/ – Apocalisp

2

GetLeg() muss Leg als Override zurückgeben. Ihre Hundeklasse kann jedoch immer noch DogLeg-Objekte zurückgeben, da sie eine Kindklasse von Leg sind. Kunden können diese dann als Doglegs verwenden und bearbeiten.

public class ClientObj{ 
    public void doStuff(){ 
    Animal a=getAnimal(); 
    if(a is Dog){ 
     DogLeg dl = (DogLeg)a.GetLeg(); 
    } 
    } 
} 
6

Hund sollte ein Bein zurückgeben, nicht ein DogLeg als Rückgabetyp. Die tatsächliche Klasse kann ein DogLeg sein, aber der Punkt ist, zu entkoppeln, damit der Benutzer von Dog nicht über DogLegs wissen muss, sie müssen nur über Beine wissen.

Wechsel:

class Dog : Animal 
{ 
    public override DogLeg GetLeg() {...} 
} 

zu:

class Dog : Animal 
{ 
    public override Leg GetLeg() {...} 
} 

dies nicht tun:

if(a instanceof Dog){ 
     DogLeg dl = (DogLeg)a.GetLeg(); 

es Niederlagen der Zweck der Programmierung auf die abstrakte Art.

Der Grund, DogLeg zu verbergen, ist, weil die Funktion GetLeg in der abstrakten Klasse ein Abstraktes Bein zurückgibt. Wenn Sie das GetLeg außer Kraft setzen, müssen Sie ein Leg zurückgeben. Das ist der Punkt, eine Methode in einer abstrakten Klasse zu haben. Um diese Methode zu ihren Kindern zu verbreiten. Wenn Sie möchten, dass die Benutzer des Hundes über DogLegs Bescheid wissen, machen Sie eine Methode namens GetDogLeg und geben Sie ein DogLeg zurück.

Wenn Sie tun können, wie der Fragesteller möchte, dann müsste jeder Benutzer von Tier über alle Tiere wissen.

+0

Nicht relevant. Die Frage fragt, warum ein Subtyp eine Methode mit einer Methode überschreiben kann, deren Rückgabe ein Subtyp der Rückgabe des Überschreibungsprogramms ist. –

+1

Ich kann das Argument verstehen, dass der Benutzer von Animal nur über Leg-Objekte Bescheid wissen sollte, aber ich bin weniger überzeugt, dass dies vor den Benutzern der Dog-Klasse verborgen werden sollte. – Luke

0

Richtig, ich verstehe, dass ich nur werfen kann, aber das bedeutet der Kunde muss wissen, dass Hunde DogLegs haben. Ich frage mich, ob es technische Gründe gibt, warum dies nicht möglich ist, da eine implizite Konvertierung existiert.

0

@Brian Leahy Offensichtlich, wenn Sie nur als ein Bein auf es operieren, gibt es keine Notwendigkeit oder Grund zu werfen. Aber wenn es ein DogLeg- oder Dog-spezifisches Verhalten gibt, gibt es manchmal Gründe dafür, dass die Besetzung notwendig ist.

0

@Luke

beschrieben

Ich denke, Ihr vielleicht Mißverständnis Erbe. Dog.GetLeg() wird ein DogLeg-Objekt zurückgeben.

public class Dog{ 
    public Leg GetLeg(){ 
     DogLeg dl = new DogLeg(super.GetLeg()); 
     //set dogleg specific properties 
    } 
} 


    Animal a = getDog(); 
    Leg l = a.GetLeg(); 
    l.kick(); 

die tatsächliche Methode aufgerufen wird Dog.GetLeg(); und DogLeg.Kick() (ich nehme eine Methode an, für die Leg.kick() existiert), da der deklarierte Rückgabetyp DogLeg unnötig ist, weil das zurückgegeben wird, auch wenn der Rückgabetyp für Dog.GetLeg() ist Bein.

0

Sie können auch die Schnittstelle ILeg zurückgeben, die beide Leg und/oder DogLeg implementieren.

3
abstract class Animal 
{ 
    public virtual Leg GetLeg() 
} 

abstract class Leg { } 

class Dog : Animal 
{ 
    public override Leg GetLeg() { return new DogLeg(); } 
} 

class DogLeg : Leg { void Hump(); } 

es so tun, dann können Sie die Abstraktion in der Client nutzen:

Leg myleg = myDog.GetLeg(); 

Dann, wenn Sie benötigen, können Sie es werfen kann:

if (myleg is DogLeg) { ((DogLeg)myLeg).Hump()); } 

total gekünstelt, aber der Punkt ist, so können Sie dies tun:

foreach (Animal a in animals) 
{ 
    a.GetLeg().SomeMethodThatIsOnAllLegs(); 
} 

Während weiterhin die Möglichkeit besteht, eine spezielle Buck-Methode auf Doglegs anzuwenden.

1

Vielleicht ist es einfacher, das Problem mit einem Beispiel zu sehen:

Animal dog = new Dog(); 
dog.SetLeg(new CatLeg()); 

das sollte nun kompilieren, wenn Sie Hund sind zusammengestellt, aber wir eine solche Mutante wahrscheinlich nicht wollen.

Ein verwandtes Problem ist sollte Hund [] ein Tier sein [] oder IList < Hund> ein IList < Tier>?

0

Die wichtige Sache zu erinnern ist, dass Sie einen abgeleiteten Typen verwenden können jeder Ort, den Sie den Basistyp verwenden (Sie Hund zu jeder Methode/Eigenschaft/Feld/Variable übergeben können, das Tier erwartet)

Lassen Sie sich diese Funktion übernehmen :

public void AddLeg(Animal a) 
{ 
    a.Leg = new Leg(); 
} 

Eine absolut gültige Funktion, jetzt wollen wir die Funktion so nennen:

AddLeg(new Dog()); 

Wenn die Eigenschaft Dog.Leg nicht vom Typ Leg die Funktion s AddLeg ist uddenly enthält einen Fehler und kann nicht kompiliert werden.

2

Nicht, dass es viel nutzt, aber es ist vielleicht interessant zu bemerken, dass Java kovariante Renditen unterstützt, und das würde genau so funktionieren, wie Sie es erhofft haben.Außer offensichtlich, dass Java keine Eigenschaften hat;)

12

Klar, Sie werden eine Besetzung benötigen, wenn Sie auf einem gebrochenen DogLeg arbeiten.

+1

hahaha nettes Spiel auf Wörter :) –

3

Sie können Generika und Schnittstellen verwenden, die in C# zu implementieren:

abstract class Leg { } 

interface IAnimal { Leg GetLeg(); } 

abstract class Animal<TLeg> : IAnimal where TLeg : Leg 
{ public abstract TLeg GetLeg(); 
    Leg IAnimal.GetLeg() { return this.GetLeg(); } 
} 

class Dog : Animal<Dog.DogLeg> 
{ public class DogLeg : Leg { } 
    public override DogLeg GetLeg() { return new DogLeg();} 
} 
+0

Seien Sie sich bewusst, dass diese elegant aussehende Lösung zu Komplexitäten aufgrund von C# -Mangel führen kann generische Varianzunterstützung. –

+0

welche Art von Komplexitäten? Diese Lösung erfordert keine Varianz. –

+0

Was ist, wenn wir einen komplexeren Hund brauchen? Eine mit DogTail, DogTeeth, DogEtc .. – Seiti

4

Es ist ein absolut gültiger Wunsch, die Unterschrift ein überwiegendes Methode einen Rückgabetyp hat zu haben, die ein Subtyp des Rückgabetypen in der ist überschriebene Methode (phew). Immerhin sind sie laufzeittypkompatibel.

Aber C# unterstützt noch nicht "kovariante Rückgabetypen" in überschriebenen Methoden (anders als C++ [1998] & Java [2004]).

Sie werden um arbeiten müssen und für die absehbare Zukunft tun, als Eric Lippert in his blog angegeben [19. Juni 2008]:

Diese Art von Varianz „Rückgabetyp Kovarianz“ genannt wird, .

wir haben keine Pläne, diese Art von Varianz in C# zu implementieren.

0

Sie erreichen können, was Sie mit einer entsprechenden Einschränkung durch die Verwendung eines generischen möchten, wie folgt aus:

abstract class Animal<LegType> where LegType : Leg 
{ 
    public abstract LegType GetLeg(); 
} 

abstract class Leg { } 

class Dog : Animal<DogLeg> 
{ 
    public override DogLeg GetLeg() 
    { 
     return new DogLeg(); 
    } 
} 

class DogLeg : Leg { } 
1

C# expliziten Schnittstellenimplementierungen nur dieses Problem zu lösen hat:

abstract class Leg { } 
class DogLeg : Leg { } 

interface IAnimal 
{ 
    Leg GetLeg(); 
} 

class Dog : IAnimal 
{ 
    public override DogLeg GetLeg() { /* */ } 

    Leg IAnimal.GetLeg() { return GetLeg(); } 
} 

Wenn Sie einen Hund über eine Referenz vom Typ Hund haben, gibt der Aufruf von GetLeg() einen DogLeg zurück. Wenn Sie das gleiche Objekt haben, aber die Referenz vom Typ IAnimal ist, wird ein Bein zurückgegeben.

Verwandte Themen