Domanda

Sto riscontrando alcuni problemi di ereditarietà poiché ho un gruppo di classi astratte intercorrelate che devono essere sovrascritte insieme per creare un'implementazione client.Idealmente vorrei fare qualcosa del genere:

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

abstract class Leg { }

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

class DogLeg : Leg { }

Ciò consentirebbe a chiunque utilizzi la classe Dog di ottenere automaticamente DogLegs e chiunque utilizzi la classe Animal di ottenere Legs.Il problema è che la funzione sottoposta a override deve avere lo stesso tipo della classe base, quindi non verrà compilata.Non vedo perché non dovrebbe, dato che DogLeg è implicitamente lanciabile su Leg.So che ci sono molti modi per aggirare questo problema, ma sono più curioso di sapere perché questo non è possibile/implementato in C#.

MODIFICARE:L'ho leggermente modificato, poiché in realtà sto utilizzando proprietà anziché funzioni nel mio codice.

MODIFICARE:L'ho cambiato di nuovo in funzioni, perché la risposta si applica solo a quella situazione (covarianza sul parametro valore della funzione impostata di una proprietà non dovrebbe lavoro).Ci scusiamo per le fluttuazioni!Mi rendo conto che molte risposte sembrano irrilevanti.

È stato utile?

Soluzione

La risposta breve è che GetLeg è invariante nel tipo restituito.La risposta lunga può essere trovata qui: Covarianza e controvarianza

Vorrei aggiungere che mentre l'ereditarietà è solitamente il primo strumento di astrazione che la maggior parte degli sviluppatori tira fuori dalla propria cassetta degli attrezzi, è quasi sempre possibile utilizzare invece la composizione.La composizione richiede un po' più di lavoro per lo sviluppatore dell'API, ma rende l'API più utile per i suoi consumatori.

Altri suggerimenti

Chiaramente, avrai bisogno di un cast se stai operando su un Dogleg rotto.

Dog dovrebbe restituire una Leg e non una DogLeg come tipo di restituzione.La classe effettiva potrebbe essere DogLeg, ma il punto è disaccoppiare in modo che l'utente di Dog non debba conoscere DogLeg, deve solo conoscere Legs.

Modifica:

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

A:

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

Non farlo:

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

vanifica lo scopo della programmazione di tipo astratto.

Il motivo per nascondere DogLeg è perché la funzione GetLeg nella classe astratta restituisce una gamba astratta.Se stai sovrascrivendo GetLeg devi restituire una Leg.Questo è il punto di avere un metodo in una classe astratta.Per propagare quel metodo ai suoi figli.Se vuoi che gli utenti di Dog conoscano DogLeg, crea un metodo chiamato GetDogLeg e restituisce un DogLeg.

Se POTRESTI fare quello che vuole chi ha posto la domanda, allora ogni utente di Animal dovrebbe conoscere TUTTI gli animali.

È un desiderio perfettamente valido che la firma di un metodo sottoposto a override abbia un tipo restituito che sia un sottotipo del tipo restituito nel metodo sottoposto a override (uff).Dopotutto, sono compatibili con il tipo di runtime.

Ma C# non supporta ancora i "tipi restituiti covarianti" nei metodi sovrascritti (a differenza di C++ [1998] e Java [2004]).

Dovrai aggirare il problema e arrangiarti per il prossimo futuro, come ha affermato Eric Lippert il suo blog[19 giugno 2008]:

Questo tipo di varianza è chiamata "covarianza del tipo di rendimento".

non abbiamo intenzione di implementare questo tipo di variazione in C#.

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

Fallo in questo modo, quindi potrai sfruttare l'astrazione nel tuo client:

Leg myleg = myDog.GetLeg();

Quindi, se necessario, puoi trasmetterlo:

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

Totalmente inventato, ma il punto è che puoi farlo:

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

Pur mantenendo la possibilità di avere uno speciale metodo Gobba su Doglegs.

È possibile utilizzare generici e interfacce per implementarlo in C#:

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

GetLeg() deve restituire Leg per essere un override.La tua classe Dog, tuttavia, può comunque restituire oggetti DogLeg poiché sono una classe figlia di Leg.i client possono quindi lanciarli e operare su di essi come dogleg.

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

Non che sia molto utile, ma forse è interessante notare che Java supporta i rendimenti covarianti, e quindi funzionerebbe esattamente come speravi.Tranne ovviamente che Java non ha proprietà ;)

Forse è più semplice capire il problema con un esempio:

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

Ora dovrebbe essere compilato se sei compilato da Dog, ma probabilmente non vogliamo un simile mutante.

Un problema correlato è Dog[] essere un Animal[] o IList<Dog> un IList<Animal>?

C# dispone di implementazioni di interfaccia esplicite per risolvere proprio questo problema:

abstract class Leg { }
class DogLeg : Leg { }

interface IAnimal
{
    Leg GetLeg();
}

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

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

Se hai un Dog tramite un riferimento di tipo Dog, la chiamata a GetLeg() restituirà un DogLeg.Se hai lo stesso oggetto, ma il riferimento è di tipo IAnimal, restituirà una Leg.

Giusto, capisco che posso semplicemente lanciare, ma ciò significa che il cliente deve sapere che i cani hanno DogLegs.Quello che mi chiedo è se ci sono ragioni tecniche per cui ciò non è possibile, dato che esiste una conversione implicita.

@Brian Leahy Ovviamente se stai opera solo come una gamba non c'è bisogno o motivo di lanciare.Ma se c'è qualche comportamento specifico di DogLeg o Dog, a volte ci sono ragioni per cui il cast è necessario.

Potresti anche restituire l'interfaccia ILeg implementata sia da Leg che da DogLeg.

La cosa importante da ricordare è che puoi usare un tipo derivato ogni volta che usi il tipo base (puoi passare Dog a qualsiasi metodo/proprietà/campo/variabile che si aspetta Animal)

Prendiamo questa funzione:

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

Una funzione perfettamente valida, ora chiamiamo la funzione in questo modo:

AddLeg(new Dog());

Se la proprietà Dog.Leg non è di tipo Leg, la funzione AddLeg contiene improvvisamente un errore e non può essere compilata.

@Luca

Penso che la tua eredità forse sia stata fraintesa.Dog.GetLeg() restituirà un oggetto DogLeg.

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

il metodo effettivamente chiamato sarà Dog.GetLeg();e DogLeg.Kick() (suppongo che esista un metodo Leg.kick()) per questo, il tipo restituito dichiarato essendo DogLeg non è necessario, perché questo è ciò che viene restituito, anche se il tipo restituito per Dog.GetLeg() è Gamba.

Puoi ottenere ciò che desideri utilizzando un generico con un vincolo appropriato, come il seguente:

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 { }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top