2009-06-16 20 views
33

Ho un dizionario per mappare un determinato tipo a un determinato oggetto generico per quel tipo. Ad esempio:Trasmissione al tipo generico in C#

typeof(LoginMessage) maps to MessageProcessor<LoginMessage> 

Ora il problema è recuperare questo oggetto generico in fase di esecuzione dal Dizionario. O per essere più specifici: per trasmettere l'oggetto recuperato al tipo generico specifico.

ho bisogno di lavorare qualcosa di simile:

Type key = message.GetType(); 
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>; 

La speranza c'è una soluzione facile a questo.

Modifica: Non voglio utilizzare Ifs e interruttori. A causa di problemi di prestazioni, non posso neanche usare riflessioni di qualche tipo.

+0

A seguito di domanda ha uno scenario di fusione evitato di usare farmaci generici - [Codice refactoring per evitare Type Casting] (http://stackoverflow.com/questions/21482850/refactoring-code-to-avoid-type-casting) – Lijo

risposta

29

fa questo lavoro per voi?

interface IMessage 
{ 
    void Process(object source); 
} 

class LoginMessage : IMessage 
{ 
    public void Process(object source) 
    { 
    } 
} 

abstract class MessageProcessor 
{ 
    public abstract void ProcessMessage(object source, object type); 
} 

class MessageProcessor<T> : MessageProcessor where T: IMessage 
{ 
    public override void ProcessMessage(object source, object o) 
    { 
     if (!(o is T)) { 
      throw new NotImplementedException(); 
     } 
     ProcessMessage(source, (T)o); 
    } 

    public void ProcessMessage(object source, T type) 
    { 
     type.Process(source); 
    } 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     Dictionary<Type, MessageProcessor> messageProcessors = new Dictionary<Type, MessageProcessor>(); 
     messageProcessors.Add(typeof(string), new MessageProcessor<LoginMessage>()); 
     LoginMessage message = new LoginMessage(); 
     Type key = message.GetType(); 
     MessageProcessor processor = messageProcessors[key]; 
     object source = null; 
     processor.ProcessMessage(source, message); 
    } 
} 

Questo ti dà l'oggetto corretto. L'unica cosa di cui non sono sicuro è se nel tuo caso sia sufficiente averlo come un MessageProcessor astratto.

Modifica: ho aggiunto un'interfaccia IMessage. Il codice di elaborazione effettivo dovrebbe ora diventare parte delle diverse classi di messaggi che dovrebbero implementare questa interfaccia.

+0

No, perché ho bisogno dell'istanza generica specifica che ha il metodo generico sicuro di tipo. Purtroppo la classe base non farà il trucco;) –

+0

Ho aggiunto un esempio di come è possibile aggiungere un metodo astratto alla classe astratta e sovrascriverlo nella classe concreta. –

+0

Spero di evitare il controllo dei tipi in fase di esecuzione, ma come tutti hanno detto, sembra inevitabile. ;) Quindi dovrò andare con questo approccio. Grazie. –

4

Non puoi farlo. Potresti provare a raccontare il tuo problema da un punto di vista di più alto livello (cioè cosa esattamente vuoi ottenere con la variabile casted) per una soluzione diversa.

Si potrebbe andare con qualcosa di simile:

public abstract class Message { 
    // ... 
} 
public class Message<T> : Message { 
} 

public abstract class MessageProcessor { 
    public abstract void ProcessMessage(Message msg); 
} 
public class SayMessageProcessor : MessageProcessor { 
    public override void ProcessMessage(Message msg) { 
     ProcessMessage((Message<Say>)msg); 
    } 
    public void ProcessMessage(Message<Say> msg) { 
     // do the actual processing 
    } 
} 

// Dispatcher logic: 
Dictionary<Type, MessageProcessor> messageProcessors = { 
    { typeof(Say), new SayMessageProcessor() }, 
    { typeof(string), new StringMessageProcessor() } 
}; // properly initialized 

messageProcessors[msg.GetType().GetGenericArguments()[0]].ProcessMessage(msg); 
+0

Se questo non è possibile, c'è un altro modo per mappare un tipo ad un certo tipo generico? –

+0

@Andrej: "mappa" è un po 'vaga qui. Potresti provare a dirci cosa vorresti fare con la variabile casted? Dato che il tipo è sconosciuto al momento della compilazione, non è possibile richiamare i metodi in o così, a meno che non si codifichi con hardkey diversi tipi di chiavi ... –

+0

Ho determinati messaggi (LoginMessage, SayMessage, ...). Ogni messaggio deve essere elaborato da un MessageProcessor specifico che ha un metodo come "ProcessMessage (origine MessageSource, messaggio MessageType)". Ovviamente potrei usare la classe base dei messaggi invece dell'approccio generico, ma voglio che sia sicuro in modo tale che un processore specifico possa elaborare il messaggio solo, a suo avviso. –

7
Type type = typeof(MessageProcessor<>).MakeGenericType(key); 

Questo è il meglio che si può fare, ma senza realmente sapere che tipo è, non c'è davvero molto di più si può fare con esso.

MODIFICA: Vorrei chiarire. Sono passato dal tipo var al tipo Type. Il mio punto è, ora si può fare qualcosa di simile:

object obj = Activator.CreateInstance(type); 

obj sarà ora il tipo corretto, ma dal momento che non si sa che tipo "chiave" è in fase di compilazione, non c'è modo per lanciarla e fare qualcosa di utile con esso.

+1

Non funziona. 'var' è solo suger sintattico risolto in fase di compilazione. Il tipo di variabile 'type' sarà' System.Type'. –

+0

Sì, lo so. Sarà il tipo di MessageProcessor che sarà quindi possibile utilizzare con Activator.CreateInstance(). Il punto era che, a quel punto, non si può fare molto perché non si sa che tipo "chiave" è. – BFree

1

Come accennato, non è possibile eseguire il cast direttamente. Una possibile soluzione è quella di avere quei tipi generici ereditari da un'interfaccia non generica, nel qual caso è comunque possibile invocare metodi su di essa senza riflessione. Usando il reflection, puoi passare l'oggetto mappato a qualsiasi metodo che lo aspetta, quindi il cast verrà eseguito per te. Pertanto, se si dispone di un metodo denominato Accetta che prevede un parametro MessageProcessor come parametro, è possibile trovarlo e richiamarlo in modo dinamico.

+0

Questo codice potrebbe essere critico per le prestazioni, quindi voglio evitare l'uso di riflessioni o approcci simili. –

2

questo non è semplicemente ammessi:

Type key = message.GetType(); 
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>; 

Non è possibile ottenere un tipo generico come un valore variabile.

Dovreste fare uno switch o qualcosa:

Type key = message.GetType(); 
if (key == typeof(Foo)) 
{ 
    MessageProcessor<Foo> processor = (MessageProcessor<Foo>)messageProcessors[key]; 
    // Do stuff with processor 
} 
else if (key == typeof(Bar)) 
{ 
    MessageProcessor<bar> processor = (MessageProcessor<Bar>)messageProcessors[key]; 
    // Do stuff with processor 
} 
... 
+0

Io uso questa struttura per evitare esattamente Ifs e switch. Quindi questo è un no go. –

+1

Quindi mettilo nella tua domanda. –

8

È possibile scrivere un metodo che prende il tipo come parametro generico:

void GenericProcessMessage<T>(T message) 
{ 
    MessageProcessor<T> processor = messageProcessors[typeof(T)] 
     as MessageProcessor<T>; 

    // Call method processor or whatever you need to do 
} 

allora avete bisogno di un modo per chiamare il metodo con l'argomento generico corretta. È possibile farlo con la riflessione:

public void ProcessMessage(object message) 
{ 
    Type messageType = message.GetType(); 
    MethodInfo method = this.GetType().GetMethod("GenericProcessMessage"); 
    MethodInfo closedMethod = method.MakeGenericMethod(messageType); 
    closedMethod.Invoke(this, new object[] {message}); 
} 
+0

Come accennato, a causa di problemi di prestazioni, voglio evitare l'uso della riflessione. Questi metodi possono essere definiti hundrets of times in tempi molto brevi e devono comunque essere eseguiti molto velocemente. –

+1

Per ottimizzare questo, è possibile configurarlo in modo che il riflesso venga eseguito una sola volta per tipo di messaggio. È possibile creare un delegato che chiama il metodo generico e memorizzarlo in un dizionario, quindi al prossimo riavvio il tipo chiama semplicemente il delegato. Il modo più semplice per farlo è probabilmente compilando un'espressione lambda. –

+0

Ancora, Andrej, qualcosa da mettere nella tua domanda. –

4

Si prega di vedere se la seguente soluzione funziona per voi. Il trucco è definire un'interfaccia processore di base che prende un tipo base di messaggio.

interface IMessage 
{ 
} 

class LoginMessage : IMessage 
{ 
} 

class LogoutMessage : IMessage 
{ 
} 

class UnknownMessage : IMessage 
{ 
} 

interface IMessageProcessor 
{ 
    void PrcessMessageBase(IMessage msg); 
} 

abstract class MessageProcessor<T> : IMessageProcessor where T : IMessage 
{ 
    public void PrcessMessageBase(IMessage msg) 
    { 
     ProcessMessage((T)msg); 
    } 

    public abstract void ProcessMessage(T msg); 

} 

class LoginMessageProcessor : MessageProcessor<LoginMessage> 
{ 
    public override void ProcessMessage(LoginMessage msg) 
    { 
     System.Console.WriteLine("Handled by LoginMsgProcessor"); 
    } 
} 

class LogoutMessageProcessor : MessageProcessor<LogoutMessage> 
{ 
    public override void ProcessMessage(LogoutMessage msg) 
    { 
     System.Console.WriteLine("Handled by LogoutMsgProcessor"); 
    } 
} 

class MessageProcessorTest 
{ 
    /// <summary> 
    /// IMessage Type and the IMessageProcessor which would process that type. 
    /// It can be further optimized by keeping IMessage type hashcode 
    /// </summary> 
    private Dictionary<Type, IMessageProcessor> msgProcessors = 
           new Dictionary<Type, IMessageProcessor>(); 
    bool processorsLoaded = false; 

    public void EnsureProcessorsLoaded() 
    { 
     if(!processorsLoaded) 
     { 
      var processors = 
       from processorType in Assembly.GetExecutingAssembly().GetTypes() 
       where processorType.IsClass && !processorType.IsAbstract && 
         processorType.GetInterface(typeof(IMessageProcessor).Name) != null 
       select Activator.CreateInstance(processorType); 

      foreach (IMessageProcessor msgProcessor in processors) 
      { 
       MethodInfo processMethod = msgProcessor.GetType().GetMethod("ProcessMessage"); 
       msgProcessors.Add(processMethod.GetParameters()[0].ParameterType, msgProcessor); 
      } 

      processorsLoaded = true; 
     } 
    } 

    public void ProcessMessages() 
    { 
     List<IMessage> msgList = new List<IMessage>(); 
     msgList.Add(new LoginMessage()); 
     msgList.Add(new LogoutMessage()); 
     msgList.Add(new UnknownMessage()); 

     foreach (IMessage msg in msgList) 
     { 
      ProcessMessage(msg); 
     } 
    } 

    public void ProcessMessage(IMessage msg) 
    { 
     EnsureProcessorsLoaded(); 
     IMessageProcessor msgProcessor = null; 
     if(msgProcessors.TryGetValue(msg.GetType(), out msgProcessor)) 
     { 
      msgProcessor.PrcessMessageBase(msg); 
     } 
     else 
     { 
      System.Console.WriteLine("Processor not found"); 
     } 
    } 

    public static void Test() 
    { 
     new MessageProcessorTest().ProcessMessages(); 
    } 
} 
6

Ho avuto un problema simile. Ho una lezione;

Action<T> 

che ha una proprietà di tipo T.

Come faccio ad avere la proprietà, quando non so T? Non riesco a lanciare per azione <> se non so T.

SOLUZIONE:

Implementare un'interfaccia non generica;

public interface IGetGenericTypeInstance 
{ 
    object GenericTypeInstance(); 
} 

Ora posso cast dell'oggetto al IGetGenericTypeInstance e GenericTypeInstance restituirà la proprietà come tipo di oggetto.

+0

Tu, mio ​​caro, sei un genio, in tutto risveglio! –

13

Quanto segue sembra funzionare così, ed è un po 'più corta delle altre risposte:

T result = (T)Convert.ChangeType(otherTypeObject, typeof(T)); 
+1

Se c'è qualche motivo specifico per cui questo è stato respinto, sarei interessato a sapere perché - se questa è una cattiva pratica, non funziona o qualcos'altro? – ptrc

+2

Immagino che questo sia stato respinto perché è decisamente sbagliato. La conversione non è un casting. http://msdn.microsoft.com/en-us/library/dtb69x08.aspx Funziona solo se si implementa IConvertible e, come puoi vedere, IConvertible definisce solo le conversioni in tipi CLR: http://msdn.microsoft.com/ it-us/library/system.iconvertible.aspx –

+4

Ah, non ho capito la differenza, sapevo solo che funzionava per me. Grazie per aver spiegato. – ptrc

0

La risposta di @DanielPlaisted prima in genere funziona, ma il metodo generico deve essere pubblico o si deve usare BindingFlags.NonPublic | BindingFlags.Instance! Non posso postarlo come commento per mancanza di reputazione.

1
public delegate void MessageProcessor<T>(T msg) where T : IExternalizable; 


    virtual public void OnRecivedMessage(IExternalizable msg) 
    { 
     Type type = msg.GetType(); 
     ArrayList list = processors.Get(type); 
     if (list != null) 
     { 
      object[] args = new object[]{msg}; 
      for (int i = list.Count - 1; i >= 0; --i) 
      { 
       Delegate e = (Delegate)list[i]; 
       e.Method.Invoke(e.Target, args); 
      } 
     } 
    } 
+0

Ciao! Benvenuti nel sito! Ti dispiacerebbe descrivere cosa fa la tua soluzione per renderla più chiara? – tjons

0

Ho faticato a risolvere un problema simile attorno alle classi delle tabelle di dati anziché ai messaggi. Il problema di root menzionato sopra di trasmettere una versione non generica della classe a una versione generica derivata era la stessa.

Per consentire l'inserimento in una libreria di classi portatile che non supportava le librerie di database, ho introdotto un set di classi di interfaccia, con l'intento di poter passare un tipo e ottenere un generico corrispondente. Ha finito per dover implementare un metodo generico.

// Interface for injection 
public interface IDatabase 
{ 
    // Original, non-functional signature: 
    IDatatable<object> GetDataTable(Type dataType); 

    // Functional method using a generic method: 
    IDatatable<T> GetDataTable<T>(); 
} 

E questa l'intera implementazione utilizzando il metodo generico di cui sopra.

La classe generica che verrà trasmessa da un dizionario.

// Non-generic base class allows listing tables together 
abstract class Datatable 
{ 
    Datatable(Type storedClass) 
    { 
     StoredClass = storedClass; 
    } 

    Type StoredClass { get; private set; } 
} 

// Generic inheriting class 
abstract class Datatable<T>: Datatable, IDatatable<T> 
{ 
    protected Datatable() 
     :base(typeof(T)) 
    { 
    } 
} 

Questa è la classe che memorizza la classe generica e getta per soddisfare il metodo generico nell'interfaccia

class Database 
{ 
    // Dictionary storing the classes using the non-generic base class 
    private Dictionary<Type, Datatable> _tableDictionary; 

    protected Database(List<Datatable> tables) 
    { 
     _tableDictionary = new Dictionary<Type, Datatable>(); 
     foreach (var table in tables) 
     { 
      _tableDictionary.Add(table.StoredClass, table); 
     } 
    } 

    // Interface implementation, casts the generic 
    public IDatatable<T> GetDataTable<T>() 
    { 
     Datatable table = null; 

     _tableDictionary.TryGetValue(typeof(T), out table); 

     return table as IDatatable<T>; 
    } 
} 

E infine la chiamata del metodo di interfaccia.

IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>(); 
Problemi correlati