2016-02-17 13 views
5

Ho una situazione interessante in cui mi piacerebbe utilizzare una classe base che utilizza un parametro di tipo per implementare un'interfaccia e anche mantenere le cose asciutte con le classi ereditarie.Implementazione generica dell'interfaccia con il tipo specificato

public interface ICalculator 
{ 
    void Process(ICalculationModel calculationModel); 
} 

public abstract class CalculatorBase<T> :ICalculator where T : ICalculationModel 
{ 
    // Compiler moans that Process(ICalculationModel calculationModel) isn't implemented 
    public abstract void Process(T calculationModel); 
} 

public class PipeworkInspections : CalculatorBase<GasSafetyModel> 
{ 
    public override void Process(GasSafetyModel documentModel){ 
     //snip 
    } 
} 

C'è qualcosa che mi manca con la clausola generica 'where' o qualcosa del genere? Nella mia testa questo dovrebbe funzionare. O il compilatore ha bisogno ESATTAMENTE della stessa implementazione della definizione dell'interfaccia?

Non riesco facilmente a spostare il parametro di tipo nell'ICalculator in quanto vi sono molti punti in cui viene utilizzato senza alcun requisito per il generico.

Questo ha chiarito le cose. Grazie per le informazioni. Ora ovviamente una soluzione è fare in modo che l'interfaccia prenda il parametro type. Tuttavia gli ICalculator sono usati in un numero di posti e sono indicati come ICalculator Ora ho errori del compilatore se ometto il parametro type nelle interfacce che si riferiscono a ICalculator ... C'è un modo per architettare questo che dovrebbe funzionare !?

+0

Credo che debba essere identico a come appare in "ICalculator". Tuttavia, dato che faccio principalmente roba Java e non C#, non posso dirlo con certezza. – Powerlord

+0

Questo non può funzionare dal momento che si potrebbe quindi eseguire 'ICalculator c = new PipeworkInspections(); c.Process (new OtherModel()) 'che non è sicuro. Il meglio che potreste fare sarebbe impiantare 'ICalculator.Process' e quindi eseguire il cast all'interno dell'implementazione generica. – Lee

risposta

0

Se ha funzionato allora si sarebbe in grado di dire qualcosa di simile:

PipeworkInspections pipeworks = new PipeworkInspections(); 
ICalculator calculator = pipeworks; 

NuclearPowerSafetyModel nuclearModel = new NuclearPowerSafetyModel(); 
calculator.Process(nuclearModel); // <-- Oops! 

che probabilmente non è quello che volevi ...

8

Nella mia testa questo dovrebbe funzionare.

Il problema allora è nella tua testa! :-) Questo non dovrebbe funzionare. Vediamo perché.

interface ICage 
{ 
    void Enclose(Animal animal); 
} 
class ZooCage<T> : ICage where T : Animal 
{ 
    public void Enclose(T t) { ... } 
} 
... 

var giraffePaddock = new ZooCage<Giraffe>(); 
var cage = (ICage)giraffePaddock; 
var tiger = new Tiger(); 
icage.Enclose(tiger); 

e ora c'è una tigre nel paddock giraffa, e la vita è un bene per la tigre, ma un male per le giraffe. Ecco perché questo è illegale.

Oppure il compilatore ha bisogno ESATTAMENTE della stessa implementazione della definizione di interfaccia?

Il membro che implementa un membro di interfaccia deve corrispondere esattamente alla firma del metodo implementato. Ad esempio, non è possibile utilizzare covarianza del tipo restituito:

interface I 
{ 
    Animal GetAnimal(); 
} 
class C : I 
{ 
    public Giraffe GetAnimal() { ... } // not legal. 
} 

Il contratto richiede un animale; fornisci una giraffa Questo dovrebbe funzionare, logicamente, ma questo non è legale in C#. (È in C++.)

Vedere le molte domande in questo sito sulla covarianza del tipo di ritorno per i motivi.

Analogamente per tipo di parametro controvarianza:

interface I 
{ 
    void PutMammal (Mammal mammal); 
} 
class C : I 
{ 
    public PutMammal(Animal animal) { ... } // not legal. 
} 

Di nuovo, questo è logicamente sensibile; il contratto richiede che tu prenda un mammifero, e questo richiede qualsiasi animale. Ma ancora, questo non è legale.

Ci sono alcune operazioni covarianti e controvarianti in C#; vedere una qualsiasi delle numerose domande su questi argomenti su questo sito, o sfogliare gli articoli di covarianza e controvarianza su ericlippert.com o il mio precedente blog msdn.

+0

Conosci qualche linguaggio che permetta l'implementazione di un'interfaccia (o l'equivalente morale) come controverso rispetto agli argomenti del metodo? Ho sentito parlare di diversi con covarianza di tipo restituito (hai appena citato C++), ma non la contravarianza dei parametri. – Servy

+0

@Servy: Eiffel forse? Mi sembra di ricordare di aver sentito qualcosa su Eiffel che ha questa caratteristica. Non ho mai usato Eiffel, quindi non posso dirlo con certezza. C# naturalmente consente la controvarianza dei parametri quando si converte un metodo in un delegato, che è in qualche modo un equivalente morale, suppongo; puoi pensare a un delegato come interfaccia con un singolo metodo chiamato Invoke. –

+0

@Servy: l'ho cercato. Eiffel supporta * parametro non sicuro * covarianza *, motivo per cui è rimasto bloccato nella mia testa. Sather è un linguaggio simile a quello di Eiffel che supporta la contravarianza dei parametri. –

0

L'interfaccia dice ogni classe attuazione sarà fornire questo metodo:

void Process(ICalculationModel calculationModel); 

Ora, ovviamente PipeworkInspections fa non. Non ha un metodo Process che accetta qualsiasi ICalculationModel. Solo IT ha un metodo che accetta le implementazioni specifiche diICalculationModel. Quindi la tua compilation fallisce.

0

Sì, è necessaria l'implementazione esatta.

In alternativa è possibile effettuare interface e Process metodo generico se funziona per voi:

public interface ICalculator<T> where T : ICalculationModel 
{ 
    void Process(T calculationModel); 
} 

public abstract class CalculatorBase<T> : ICalculator where T : ICalculationModel 
{ 
    public abstract void Process(T calculationModel); 
} 
0

Sono d'accordo con la risposta di Eric Lippert: non si può. E ha spiegato in modo molto efficace perché questo accade.

Se davvero vuole fare questo, è possibile aggiungere il seguente alla classe astratta, e sarà la compilazione:

void ICalculator.Process(ICalculationModel calcMod) 
{ 
    Process((T)calcMod); 
} 

Ma è necessario sapere cosa si sta facendo, altrimenti si potrebbe avere alcuni InvalidCastException in fase di esecuzione.

Problemi correlati