Warum funktioniert die Vererbung nicht so, wie ich denke, dass sie funktionieren sollte?

8

Ich habe einige Probleme mit der Vererbung, da ich eine Gruppe von zusammenhängenden abstrakten Klassen habe, die alle zusammen überschrieben werden müssen, um eine Client-Implementierung zu erstellen. Idealerweise würde ich gerne Folgendes machen:

%Vor%

Dies würde es jedem ermöglichen, die Dog-Klasse zu benutzen, um DogLegs und jeden, der die Animal-Klasse benutzt, automatisch Legs 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 das etwas modifiziert, da ich eigentlich Eigenschaften anstelle von Funktionen in meinem Code verwende.

BEARBEITEN : Ich habe es wieder in Funktionen geändert, weil die Antwort nur auf diese Situation zutrifft (Kovarianz auf dem Wert-Parameter der Set-Funktion einer Eigenschaft sollte nicht funktionieren) . Entschuldigung für die Schwankungen! Mir ist klar, dass viele Antworten irrelevant erscheinen.

    
Luke 05.09.2008, 21:31
quelle

17 Antworten

15

Die kurze Antwort ist, dass GetLeg in seinem Rückgabetyp invariant ist. Die lange Antwort finden Sie hier: Kovarianz und Kontravarianz

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

    
Apocalisp 05.09.2008, 21:37
quelle
12

Natürlich wirst du eine Besetzung brauchen, wenn du es bist operiert auf einem gebrochenen DogLeg.

    
vextasy 05.09.2008 22:38
quelle
6

Hund sollte als Rückgabetyp ein Bein und kein Hundeleg zurückgeben. 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.

Ändern:

%Vor%

zu:

%Vor%

Tu das nicht:

%Vor%

es vereitelt den Zweck der Programmierung für den abstrakten Typ.

Der Grund, DogLeg zu verbergen, ist, weil die GetLeg-Funktion 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önnten, was der Fragesteller möchte, dann müsste jeder Benutzer von Animal über ALLE Tiere Bescheid wissen.

    
Brian Leahy 05.09.2008 21:35
quelle
4

Es ist ein vollkommen gültiger Wunsch, dass die Signatur eine überschreibende Methode einen Rückgabetyp hat, der ein Untertyp des Rückgabetyps in der überschriebenen Methode ist ( puh ). Immerhin sind sie laufzeittypkompatibel.

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

Sie müssen umgehen und auf absehbare Zeit auskommen, wie Eric Lippert in sein Blog  [19. Juni 2008]:

  

Diese Art von Varianz wird "Kovarianz vom Rückgabetyp" genannt.

     

Wir haben nicht vor, diese Art von Varianz in C # zu implementieren.

    
Ivan Hamilton 06.09.2008 06:57
quelle
3
%Vor%

Tun Sie es so, dann können Sie die Abstraktion in Ihrem Klienten benutzen:

%Vor%

Dann können Sie es bei Bedarf umsetzen:

%Vor%

Völlig erfunden, aber der Punkt ist, dass Sie das tun können:

%Vor%

Sie haben zwar weiterhin die Möglichkeit, eine spezielle Buck-Methode für Doglegs zu verwenden.

    
FlySwat 05.09.2008 21:48
quelle
3

Sie können Generics und Schnittstellen verwenden, um das in C # zu implementieren:

%Vor%     
Mark Cidade 06.09.2008 04:37
quelle
2

GetLeg () muss Leg zurückgeben, um eine Überschreibung zu sein. Ihre Hundeklasse kann jedoch immer noch DogLeg-Objekte zurückgeben, da sie eine Kindklasse von Leg sind. Clients können sie dann als Doglegs verwenden und bearbeiten.

%Vor%     
shsteimer 05.09.2008 21:33
quelle
2

Das Konzept, das Ihnen Probleme verursacht, ist unter Ссылка

beschrieben     
ben 05.09.2008 21:38
quelle
2

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

    
serg10 05.09.2008 22:03
quelle
1

Vielleicht ist es einfacher, das Problem anhand eines Beispiels zu sehen:

%Vor%

Nun, das sollte kompilieren, wenn Sie Dog kompiliert sind, aber wir wollen wahrscheinlich keine solche Mutante.

Ein verwandtes Problem ist, sollte Dog [] ein Animal [] sein, oder IList & lt; Dog & gt; ein IList & lt; Animal & gt;?

    
Tom Hawtin - tackline 05.09.2008 21:54
quelle
1

C # verfügt über explizite Schnittstellenimplementierungen, um genau dieses Problem zu beheben:

%Vor%

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

    
munificent 16.09.2008 15:39
quelle
0

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

    
Luke 05.09.2008 21:36
quelle
0

@ Brian Leahy Offensichtlich, wenn Sie nur als ein Bein darauf operieren, gibt es keinen Grund 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.

    
shsteimer 05.09.2008 21:38
quelle
0

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

    
Keith Sirmons 05.09.2008 21:47
quelle
0

Wichtig ist, dass Sie an jeder Stelle, an der Sie den Basistyp verwenden, einen abgeleiteten Typ verwenden können (Sie können Dog an jede Methode / Eigenschaft / Feld / Variable übergeben, die Animal erwartet)

Nehmen wir diese Funktion:

%Vor%

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

%Vor%

Wenn die Eigenschaft Dog.Leg nicht vom Typ Leg ist, enthält die AddLeg-Funktion plötzlich einen Fehler und kann nicht kompiliert werden.

    
Nir 05.09.2008 21:54
quelle
0

@Luke

Ich denke Ihre vielleicht missverstandene Erbschaft. Dog.GetLeg () wird ein DogLeg-Objekt zurückgeben.

%Vor%

Die aufgerufene Methode heißt 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.

    
shsteimer 05.09.2008 21:46
quelle
0

Sie können erreichen, was Sie wollen, indem Sie ein generisches Element mit einer geeigneten Einschränkung wie folgt verwenden:

%Vor%     
Saeed 16.09.2008 00:46
quelle