2014-12-16 16 views
6

Voglio costruire un sistema generico di produttori e consumatori per un progetto semplice.ereditarietà di interfacce con elenchi generici

Quello che ho ora è

public interface IMessage  { } 
public interface Message1 : IMessage { } 
public interface Message2 : IMessage { } 
public interface IConsumer<T> { } 
public interface IProducer<T> { } 

public class Mop1 : IConsumer<Message1>, IProducer<Message2> 
{ 
} 



class Program 
{ 
    static void Main(string[] args) 
    { 
     var list = new List<IConsumer<IMessage>>(); 
     var mop = new Mop1(); 
     list.Add(mop); // Error occurs here 
    } 
} 

L'ultima riga dà un errore come cannot convert from 'Mop1' to 'IConsumer<GenericPubSub.IMessage>'

Ma MOP1 implementa un IConsumer di un tipo derivato IMessage. C'è qualche problema di varianza qui? Cosa c'è di sbagliato in questo?

risposta

4

Nel caso si preveda di implementare un tipico produttore, modello di consumo con queste interfacce, vedere prima this answer.


Se si desidera un IConsumer<TMessage>-anche essere un IConsumer<TMessageBase>, dove TMessageBase è un qualsiasi tipo che eredita TMessage o attrezzi, allora avete bisogno per rendere il vostro parametro covariante generico. Utilizzare il modificatore out.

public interface IConsumer<out T> { } 

Ora IConsumer<Message1> è assegnabile a IConsumer<IMessage>, dal momento che Message1 implementa IMessage.

+0

È davvero fantastico. –

+2

Tranne che non è esattamente questo, anche se compila.Il consumatore prende i messaggi, quindi dovrebbe essere "in" ma non verrà compilato. La logica originale è rotta. – Andrey

+0

@Andrey Dovremmo esaminare alcune firme dei metodi per capire cosa dovrebbe essere dentro e fuori. In questo momento le interfacce sono completamente vuote. –

5

Questo è un caso complicato. Puoi usare co/controvarianza, ma ...

semplificherò un po 'il codice. Giusto per chiudere il compilatore si può fare:

public interface IMessage { } 

public interface Message1 : IMessage { } 


public class Mop1 : IConsumer<Message1> 
{ 
} 

public interface IConsumer<out IMessage> 
{ 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     var list = new List<IConsumer<IMessage>>(); 
     var mop = new Mop1(); 

     list.Add(mop); // Error occurs here 
    } 
} 

out IMessage farà il trucco come suggerito in un'altra risposta, ma fondamentalmente non risolvere nulla. Mi permetta di mostrare, ora si vuole fare l'interfaccia:

public interface IConsumer<out IMessage> 
{ 
    object Process (IMessage m); 
} 

aaaand non si compila. Perché semplicemente messo se dici out IMessage significa che i tipi di ritorno dovrebbero essere derivati ​​da IMessage, non i tipi di parametri.

in modo da avere in questo modo:

public interface IConsumer<in IMessage> 
{ 
    object Process (IMessage m); 
} 

public class Mop1 : IConsumer<Message1> 
{ 
    public object Process (Message1 msg) 
    { 
     return null; 
    } 
} 

Per rendere più compilare e valido. Ma ora il tuo list.Add(mop); non funzionerà. E giustamente, perché il vostro Mop1 non è infatti calcinabile per IConsumer<IMessage> perché se stava seguendo il codice sarebbe possibile:

list[0].Process (new Message2()); 

e non è possibile perché MOP1 solo Message1 accetta.

Quindi per rispondere alla domanda. Non si può davvero fare nulla di significativo con l'invio di messaggi e le caratteristiche del compilatore C#. Vedo dove stava andando, volevi una battitura statica con messaggi e cose. Purtroppo non è possibile avere un elenco di consumatori che consumano messaggi specifici nell'elenco generico, è necessario disporre di firme astratte come bool CanProcess(IMessage); IMessage Process(IMessage); ed eseguire il cast all'interno. Puoi anche farlo leggermente meglio con gli attributi personalizzati e la riflessione, ma ancora una volta, non solo con il compilatore C#.

+0

Supponendo che questo è il modo in cui l'OP stava progettando di implementare le loro interfacce (che ammetto molto probabilmente), questa è una risposta molto più utile alla domanda. –

+0

@Asad solo presupposto Ho fatto che 'Consumer' consuma, cioè accetta. – Andrey

+0

Il nostro ipotetico' IConsumer .Consumi' potrebbe utilizzare un parametro di messaggio dinamico ed essere un'interfaccia marcatore, per quanto ne sai. Come ho detto, ci sono molte cose che stai ipotizzando basate su un'interfaccia vuota. –