2009-06-16 4 views
33

J'ai un dictionnaire pour mapper un certain type à un certain objet générique pour ce type. Par exemple:Transmettre au type générique en C#

typeof(LoginMessage) maps to MessageProcessor<LoginMessage> 

Le problème est maintenant de récupérer cet objet générique à l'exécution à partir du dictionnaire. Ou pour être plus précis: Pour convertir l'objet récupéré en type générique spécifique.

je en ai besoin de travailler quelque chose comme ceci:

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

Hope il y a une solution facile à ce sujet.

Éditer: Je ne souhaite pas utiliser les Ifs et les commutateurs. En raison de problèmes de performance, je ne peux pas non plus utiliser de réflexion.

+0

La question suivante a un scénario de coulée évité en utilisant des génériques - [Code refactoring pour éviter clichage] (http://stackoverflow.com/questions/21482850/refactoring-code-to-avoid-type-casting) – Lijo

Répondre

29

Est-ce que ce travail pour vous?

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

Ceci vous donne l'objet correct. La seule chose dont je ne suis pas sûr est de savoir si c'est suffisant dans votre cas pour l'avoir en tant que MessageProcessor abstrait.

Modifier: J'ai ajouté une interface IMessage. Le code de traitement réel devrait maintenant faire partie des différentes classes de message qui devraient toutes implémenter cette interface.

+0

Non, car j'ai besoin de l'instance générique spécifique qui a la méthode générique safe. Malheureusement, la classe de base ne fera pas l'affaire;) –

+0

J'ai ajouté un exemple de comment vous pouvez ajouter une méthode abstraite à la classe abstraite et la remplacer dans la classe concrète. –

+0

J'espérais éviter la vérification de type lors de l'exécution mais comme tout le monde l'a dit, cela semble inévitable. ;) Je vais donc devoir suivre cette approche. Merci. –

4

Vous ne pouvez pas faire cela. Vous pourriez essayer de dire à votre problème d'un point de vue plus haut niveau (c'est-à-dire ce que vous voulez accomplir avec la variable casted) pour une solution différente.

Vous pouvez aller avec quelque chose comme ceci:

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 cela n'est pas possible, existe-t-il un autre moyen de mapper un type à un certain type générique? –

+0

@Andrej: "carte" est un peu vague ici. Pourriez-vous nous dire ce que vous voulez faire avec la variable casted? Comme le type est inconnu au moment de la compilation, vous ne pouvez pas appeler les méthodes dans ou ainsi, sauf si vous codez différents types 'key' ... –

+0

J'ai certains messages (LoginMessage, SayMessage, ...). Chaque message doit être traité par un MessageProcessor spécifique qui a une méthode comme "ProcessMessage (source MessageSource, message MessageType)". Je pourrais bien sûr utiliser la classe de base des Messages au lieu de l'approche générique, mais je veux que ce soit de type sécurisé afin qu'un Processeur spécifique ne puisse traiter que le message qu'il est censé faire. –

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

C'est le meilleur que vous pouvez faire, mais sans réellement savoir quel type il est, il n'y a vraiment pas beaucoup plus que vous pouvez faire avec elle.

EDIT: Je devrais clarifier. Je suis passé du type var au type Type. Mon point est, maintenant, vous pouvez faire quelque chose comme ceci:

object obj = Activator.CreateInstance(type); 

obj sera désormais le bon type, mais puisque vous ne savez pas quel type « clé » est au moment de la compilation, il n'y a aucun moyen de le jeter et faire quelque chose d'utile avec cela.

+1

Ne fonctionne pas. 'var' est juste syntaxique suger résolu au moment de la compilation. Le type de la variable 'type' sera' System.Type'. –

+0

Oui, je le sais. Ce sera le type de MessageProcessor que vous pourrez ensuite utiliser avec Activator.CreateInstance(). Mon point de vue était que, à ce moment-là, vous ne pouvez pas faire grand-chose avec ça parce que vous ne savez pas quel type de «clé» est. – BFree

1

Comme mentionné, vous ne pouvez pas le lancer directement. Une solution possible consiste à faire hériter ces types génériques d'une interface non générique, auquel cas vous pouvez toujours invoquer des méthodes sans réflexion. En utilisant la réflexion, vous pouvez passer l'objet mappé à n'importe quelle méthode qui l'attend, puis la distribution sera effectuée pour vous. Donc, si vous avez une méthode appelée Accepter un MessageProcessor en tant que paramètre, vous pouvez le trouver et l'appeler dynamiquement.

+0

Ce code peut être critique en termes de performances. Je souhaite donc éviter d'utiliser la réflexion ou des approches similaires. –

2

Cela est tout simplement permis:

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

Vous ne pouvez pas obtenir un type générique en tant que valeur variable.

Vous auriez à faire un interrupteur ou quelque chose:

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

J'utilise cette structure pour éviter exactement Ifs et les commutateurs. Donc c'est un non. –

+1

Ensuite, mettez cela dans votre question. –

8

Vous pouvez écrire une méthode qui prend le type comme paramètre générique:

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

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

Ensuite, vous avez besoin d'un moyen d'appeler la méthode avec l'argument générique correct. Vous pouvez le faire avec la réflexion:

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

Comme mentionné, en raison de problèmes de performance, je veux éviter d'utiliser la réflexion. Ces méthodes peuvent être appelées hundrets de temps dans des périodes très courtes et doivent encore fonctionner très rapidement. –

+1

Pour optimiser cela, vous pouvez le configurer de sorte que la réflexion ne soit effectuée qu'une seule fois par type de message. Vous pouvez créer un délégué qui appelle la méthode générique et la stocker dans un dictionnaire. Ainsi, la prochaine fois que le type apparaîtra, il appellera simplement le délégué. La façon la plus simple de le faire est probablement de compiler une expression lambda. –

+0

Encore une fois, Andrej, quelque chose à mettre dans votre question. –

4

S'il vous plaît voir si la solution suivante fonctionne pour vous. L'astuce consiste à définir une interface de processeur de base qui prend un type de message de 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

J'ai eu un problème similaire. J'ai un cours;

Action<T> 

qui a une propriété de type T.

Comment puis-je obtenir la propriété quand je ne sais pas T? Je ne peux pas jeter à l'action <> à moins que je sais T.

SOLUTION:

Mettre en œuvre une interface non générique;

public interface IGetGenericTypeInstance 
{ 
    object GenericTypeInstance(); 
} 

Maintenant, je peux jeter l'objet à IGetGenericTypeInstance et GenericTypeInstance renverra la propriété comme objet de type.

+0

Vous mon homme, êtes un génie, upvotes tout autour! –

13

Ce qui suit semble fonctionner aussi bien, et il est un peu plus courte que les autres réponses:

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

S'il y a une raison spécifique pour laquelle cela a été rejeté, je serais intéressé de savoir pourquoi - si c'est une mauvaise pratique, ne fonctionne pas ou quelque chose d'autre? – ptrc

+2

Je suppose que cela a été rejeté parce que c'est complètement faux. La conversion n'est pas un casting. http://msdn.microsoft.com/en-us/library/dtb69x08.aspx Cela ne fonctionne que si vous implémentez IConvertible et comme vous pouvez le voir, IConvertible ne définit que les conversions en types CLR: http://msdn.microsoft.com/ En-us/library/system.iconvertible.aspx –

+4

Ah, je n'ai pas compris la différence, je savais que ça fonctionnait pour moi. Merci d'expliquer. – ptrc

0

La réponse de @DanielPlaisted avant que les travaux en général, mais la méthode générique doit être public ou il faut utiliser BindingFlags.NonPublic | BindingFlags.Instance! Impossible de poster comme commentaire pour manque de réputation.

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

Bonjour! Bienvenue sur le site! Cela vous dérangerait-il de décrire ce que votre solution fait pour le rendre plus clair? – tjons

0

J'ai eu du mal à résoudre un problème similaire autour des classes de tables de données au lieu des messages. Le problème racine mentionné ci-dessus de la conversion d'une version non générique de la classe en une version générique dérivée était le même. Afin de permettre l'injection dans une bibliothèque de classes portable qui ne supportait pas les bibliothèques de bases de données, j'ai introduit un ensemble de classes d'interface, dans le but de pouvoir transmettre un type et obtenir un générique correspondant. Il a fini par avoir besoin d'implémenter une méthode générique.

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

Et cela l'ensemble de l'implémentation en utilisant la méthode générique ci-dessus.

La classe générique qui sera issue d'un dictionnaire.

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

C'est la classe qui stocke la classe générique et il jette pour satisfaire la méthode générique dans l'interface

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

Et enfin l'appel de la méthode d'interface.

IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>(); 
Questions connexes