2013-04-23 6 views
5

considerare le seguenti classi:metodo astratto con tipo di ritorno fortemente tipizzato

public abstract class Animal 
{ 
    public abstract Animal GiveBirth(); 
} 

public class Monkey : Animal 
{ 
    public override Animal GiveBirth() 
    { 
     return new Monkey(); 
    } 
} 

public class Snake : Animal 
{ 
    public override Animal GiveBirth() 
    { 
     return new Snake(); 
    } 
} 

//That one doesnt makes sense. 
public class WeirdHuman: Animal 
{ 
    public override Animal GiveBirth() 
    { 
     return new Monkey(); 
    } 
} 

sto cercando un modo per far rispettare le tipologie di ritorno del metodo overrided GiveBirth in modo che restituisca sempre il tipo di classe attuale, in modo che no WeirdHuman può dare alla luce un Monkey.

Mi sembra che la risposta riguardi i tipi generici, ma non riesco a vedere come posso farlo.

Esempio del risultato atteso:

public abstract class Animal 
{ 
    public abstract /*here a way to specify concrete type*/ GiveBirth(); 
} 

public class Monkey : Animal 
{ 
    public override Monkey GiveBirth() //Must returns an actual Monkey 
    { 
     return new Monkey(); 
    } 
} 

"Assolutamente impossibile" può essere una risposta, se chiaramente spiegato.

risposta

5

Questo è un ritorno in co-variante e non è supportato da C#. Mi lamento ogni giorno. Il meglio che puoi sperare di fare per aggirare il problema è usare un tipo generico di ritorno e specificare una condizione where sul tipo generico, ma questo può anche causare l'esecuzione di altri problemi lungo la strada con i corrispondenti requisiti dei parametri generici.

public abstract class Animal<TBirthType> where TBirthType : Animal<TBirthType> 
{ 
    public abstract TBirthType GiveBirth(); 
} 

public class Monkey<TBirthType> : Animal<TBirthType> where TBirthType : Monkey<TBirthType> 
{ 
    public override TBirthType GiveBirth() 
    { 
     return new Monkey<Monkey>(); 
    } 
} 

In alternativa, se non hai bisogno di qualsiasi ulteriore eredità, è possibile chiudere il generico.

public class Monkey : Animal<Monkey> 
{ 
    public override Monkey GiveBirth() 
    { 
     return new Monkey(); 
    } 
} 

noti che covarianza da sola non è ancora sufficiente a garantire che nessun tipo di comportamento anomalo derivata può essere formato, ma permetterà per il tipo di ritorno da specificare come il tipo utilizzato. Non ci sarebbe comunque un modo per bloccarlo dalla classe astratta. Potresti forse gestire un controllo runtime tramite riflessione da un metodo implementato al livello base che verrebbe controllato durante il runtime, ma potrebbe anche essere molto complicato.

+0

Questo è quello che pensavo. Non riesco a vedere alcun modo per specificare che il tipo generico deve implementare il tipo di classe effettivo. Come "dove T: questo" o qualcosa del genere ... – Johnny5

+0

Quali sono le lingue che lo supportano? – Johnny5

+0

Java lo supporta. Niente .Net lo supporta poiché il CLR è il problema di fondo. Speriamo che una versione futura aggiungerà supporto, ma, a quanto ho capito, ci vorrebbe un cambiamento sostanziale per farlo funzionare. –

1

Si può fare qualcosa di simile, che costringe gli implementatori di Animal<T> per implementare un metodo Animal<T> GiveBirth() che restituisce lo stesso tipo come il parametro di tipo, che a sua volta è costretto a essere una specie di animale.

Questo non è abbastanza quello che si vuole, ma solo così si può vedere:

public abstract class Animal<T> where T: Animal<T> 
{ 
    public abstract Animal<T> GiveBirth(); 
} 

public class Monkey: Animal<Monkey> 
{ 
    public override Animal<Monkey> GiveBirth() 
    { 
     return new Monkey(); 
    } 
} 

public class Snake: Animal<Snake> 
{ 
    public override Animal<Snake> GiveBirth() 
    { 
     return new Snake(); 
    } 
} 

public class WeirdHuman: Animal<WeirdHuman> 
{ 
    public override Animal<WeirdHuman> GiveBirth() 
    { 
     return new Monkey(); // Won't compile of course. 
    } 
} 

Se commentare la public override Animal<Monkey> GiveBirth() metodi, vedrete che il compilatore si lamenta e dice qualcosa del tipo:

errore 1 'ConsoleApplication1.Monkey' non a attuare ereditato membro astratto 'ConsoleApplication1.Animal.GiveBirth()'

Sfortunatamente, devi dichiarare le classi usando la sintassi SomeKindOfAnimal: Animal<SomeKindOfAnimal>, ma forse questo funzionerà per te.

(Also see this thread.)

Ahimè, questo lavoro non del tutto perché permette di fare questo:

public class Monkey: Animal<WeirdHuman> 
{ 
    public override Animal<WeirdHuman> GiveBirth() 
    { 
     return new WeirdHuman(); 
    } 
} 

In altre parole, essa vincola il parametro di tipo ad essere una sorta di animale, e vincola anche il tipo di ritorno di GiveBirth() per essere uguale al parametro type; ma è tutto ciò che fa.In alcuni casi questo è sufficiente, ma probabilmente non per i tuoi scopi.

Ancora, forse questo approccio è la pena conoscere.

+0

E poi cosa succede quando qualcuno fa un 'Monkey: Animal ' e fa dei bambini davvero incasinati? – Servy

+0

@Servy :) Bene, il metodo 'GiveBirth()' è ancora costretto a restituire solo un 'Monkey', quindi soddisfa il requisito per *" un modo per forzare i tipi di ritorno del metodo OverBed di GiveBirth in modo che ritorni sempre il tipo di classe effettivo * "(dall'OP). Ti è permesso dire che una Scimmia è un WeirdHuman, se vuoi! –

+1

Ma non è vincolato a restituire il tipo di classe che implementa l'interfaccia, solo qualsiasi tipo scelto dalla classe che implementa l'interfaccia. – Servy

1

Per quanto ne so, non esiste un modo pulito per supportare questo puramente in una singola gerarchia di classi. Utilizzando parametri di tipo generico ricorrenti, ad es.

public class Animal<T> where T : Animal<T> { } 

può essere accettabile se si controlla l'intera gerarchia, e può quindi escludere classi come

public class WierdHuman<Monkey> { } 

cosa si vuole veramente è qualcosa di simile typeclasses di Haskell, dove si può astrarre il tipo concreto di la classe stessa. Il più vicino che puoi ottenere in C# è definire un oggetto surrogato che implementa la funzionalità richiesta, e poi passarlo ovunque lo richieda.

Nel tuo caso, questo significa creare un'interfaccia per dare alla luce e implementarla per ogni tipo di animale concreto.

I metodi che richiedono questa funzionalità richiedono quindi un parametro aggiuntivo per l'istanza "classe tipo". Questi metodi possono limitare il tipo di animale generico a essere lo stesso:

public interface ISpawn<T> where T : Animal 
{ 
    public T GiveBirth(); 
} 

public void Populate<T>(T parent, ISpawn<T> spawn) where T : Animal 
{ 
}