2009-06-16 16 views
33

Tengo un diccionario para asignar un cierto tipo a un determinado objeto genérico para ese tipo. Por ejemplo:Transmitir a tipo genérico en C#

typeof(LoginMessage) maps to MessageProcessor<LoginMessage> 

Ahora el problema es recuperar este objeto genérico en el tiempo de ejecución del Diccionario. O para ser más específico: para convertir el objeto recuperado al tipo genérico específico.

necesito que funcione algo como esto:

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

Esperanza hay una solución fácil a este.

Edit: No quiero utilizar Ifs y switches. Debido a problemas de rendimiento, tampoco puedo usar el reflejo de ningún tipo.

+0

Siguiendo cuestión tiene un escenario de fundición evitado el uso de los genéricos - [Código Refactoring para evitar la conversión de tipos] (http://stackoverflow.com/questions/21482850/refactoring-code-to-avoid-type-casting) – Lijo

Respuesta

29

hace este trabajo para usted?

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

Esto le proporciona el objeto correcto. Lo único de lo que no estoy seguro es si es suficiente en su caso tenerlo como un Procesador de Mensajes abstracto.

Editar: He añadido una interfaz IMessage. El código de procesamiento real debería convertirse ahora en parte de las diferentes clases de mensajes que deberían implementar esta interfaz.

+0

No, porque necesito la instancia genérica específica que tiene el tipo de método genérico seguro. Lamentablemente, la clase base no hace el truco;) –

+0

Agregué un ejemplo de cómo se puede agregar un método abstracto a la clase abstracta y anularlo en la clase concreta. –

+0

Esperé evitar el control de tipos durante el tiempo de ejecución pero, como todos dijeron, parece inevitable. ;) Así que tendré que ir con este enfoque. Gracias. –

4

No puede hacer eso. Podría tratar de contar su problema desde un punto de vista de más alto nivel (es decir, qué es exactamente lo que quiere lograr con la variable moldeada) para una solución diferente.

Usted podría ir con algo como esto:

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

Si esto no es posible, ¿hay alguna otra manera de asignar un tipo a un cierto tipo genérico? –

+0

@Andrej: "mapa" es un poco vago aquí. ¿Podrías decirnos qué quieres hacer con la variable moldeada? Como el tipo es desconocido en el momento de la compilación, no puede llamar a los métodos en o menos, a menos que codifique diferentes tipos de 'clave' ... –

+0

Tengo ciertos mensajes (LoginMessage, SayMessage, ...). Cada Mensaje tiene que ser procesado por un Procesador de Mensajes específico que tiene un Método como "MensajeDeMessage (fuente de MessageSource, mensaje MessageType)". Podría, por supuesto, usar la clase base de los Mensajes en lugar de la aproximación genérica, pero quiero que esto sea seguro para que un Procesador específico solo pueda procesar el mensaje, que se supone que debe hacerlo. –

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

eso es lo mejor que puede hacer, sin embargo, sin saber realmente de qué tipo es, realmente no hay mucho más que puede hacer con él.

EDIT: Debo aclarar. Cambié de tipo var a Tipo tipo. Mi punto es, ahora se puede hacer algo como esto:

object obj = Activator.CreateInstance(type); 

obj ahora será el tipo correcto, pero ya que no sabe qué tipo "llave" es en tiempo de compilación, no hay manera de echarlo y haz algo útil con eso.

+1

No funcionará. 'var' es un suger sintáctico resuelto en tiempo de compilación. El tipo de variable 'type' será 'System.Type'. –

+0

Sí, lo sé. Será el tipo de MessageProcessor que luego puede usar con Activator.CreateInstance(). Mi punto era, sin embargo, que en ese punto, no se puede hacer mucho con él porque no se sabe qué tipo de "clave" es. – BFree

1

Como se mencionó anteriormente, no se puede convertir directamente. Una posible solución es hacer que esos tipos genéricos hereden de una interfaz no genérica, en cuyo caso puede invocar métodos sin reflexión. Usando la reflexión, puede pasar el objeto mapeado a cualquier método que lo espere, luego se realizará el lanzamiento por usted. Por lo tanto, si tiene un método llamado Aceptar esperando un MessageProcessor como parámetro, puede encontrarlo e invocarlo dinámicamente.

+0

Este código puede ser crítico para el rendimiento, por lo que quiero evitar el uso de la reflexión o enfoques similares. –

2

Esto simplemente no está permitido:

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

No se puede conseguir un tipo genérico como un valor variable.

Habría que hacer un interruptor o algo:

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

Uso esta estructura para evitar exactamente Ifs y switches. Entonces esto es un no ir. –

+1

Luego, ponga eso en su pregunta. –

8

Se puede escribir un método que toma el tipo como un parámetro genérico:

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

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

Luego hay una manera de llamar al método con el argumento genérico correcto. Usted puede hacer esto con la reflexión:

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

Como se mencionó, debido a problemas de rendimiento, quiero evitar el uso de la reflexión. Esos métodos pueden llamarse cientos de veces en tiempos muy cortos y aún así deben funcionar muy rápido. –

+1

Para optimizar esto, puede configurarlo de modo que la reflexión solo se realice una vez por tipo de mensaje. Puede crear un delegado que llame al método genérico y almacenarlo en un diccionario, de modo que la próxima vez que aparezca el tipo llame al delegado. La forma más sencilla de hacerlo es probablemente compilando una expresión lambda. –

+0

Una vez más, Andrej, algo para poner en su pregunta. –

4

Por favor, vea si la siguiente solución funciona para usted. El truco consiste en definir una interfaz de procesador base que toma un tipo de mensaje base.

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

Tuve un problema similar. Tengo una clase;

Action<T> 

que tiene una propiedad de tipo T.

¿Cómo consigo la propiedad cuando no sé T? No puedo echar a la acción <> a menos que sepa T.

SOLUCIÓN:

Implementar una interfaz no genérico;

public interface IGetGenericTypeInstance 
{ 
    object GenericTypeInstance(); 
} 

Ahora puedo enviar el objeto a IGetGenericTypeInstance y GenericTypeInstance devolverá la propiedad como tipo de objeto.

+0

¡Eres mi hombre, eres un genio, vota por todas partes! –

13

El siguiente parece funcionar tan bien, y es un poco más corto que el otro responde:

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

Si hay alguna razón específica por la que se votó negativamente, me interesaría saber por qué, si esto es una mala práctica, no funciona o alguna otra cosa. – ptrc

+2

Supongo que esto fue rechazado porque esto definitivamente es incorrecto. La conversión no es de conversión. http://msdn.microsoft.com/en-us/library/dtb69x08.aspx Solo funciona si implementa IConvertible y, como puede ver, IConvertible solo define conversiones a tipos de CLR: http://msdn.microsoft.com/ es-us/library/system.iconvertible.aspx –

+4

Ah, no entendí la diferencia, solo sabía que me funcionaba. Gracias por la explicación. – ptrc

0

La respuesta de @DanielPlaisted antes trabaja generalmente, pero el método genérico debe ser pública o uno debe utilizar BindingFlags.NonPublic | BindingFlags.Instance! No se pudo publicar como comentario por falta de reputación.

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

¡Hola! Bienvenido al sitio! ¿Le importaría describir lo que hace su solución para hacerlo más claro? – tjons

0

Tuve problemas para resolver un problema similar alrededor de las clases de la tabla de datos en lugar de los mensajes. El problema de raíz mencionado anteriormente de convertir una versión no genérica de la clase a una versión genérica derivada era el mismo.

Para permitir la inyección en una biblioteca de clases portátil que no admite bibliotecas de bases de datos, introduje un conjunto de clases de interfaz, con la intención de que pudiera pasar un tipo y obtener un genérico coincidente. Terminó necesitando implementar un método genérico.

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

Y esta toda la implementación utilizando el método genérico anterior.

La clase genérica que se emitirá desde un diccionario.

// 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)) 
    { 
    } 
} 

Esta es la clase que almacena la clase genérica y lo convierte en satisfacer el método genérico en la interfaz

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

Y finalmente la llamada del método de interfaz.

IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>(); 
Cuestiones relacionadas