web-dev-qa-db-fra.com

Cast vers le type générique en C #

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

typeof(LoginMessage) maps to MessageProcessor<LoginMessage>

Maintenant, le problème est de récupérer cet objet générique au dictionnaire à l'exécution. Ou pour être plus spécifique: Pour convertir l'objet extrait en type générique spécifique.

J'en ai besoin pour travailler quelque chose comme ça:

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

J'espère qu'il y a une solution facile à cela.

Edit: Je ne veux pas utiliser Ifs et commutateurs. En raison de problèmes de performances, je ne peux pas non plus utiliser de réflexion.

37
Andrej

Est-ce que ça marche pour toi? 

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

Cela vous donne le bon objet. La seule chose sur laquelle je ne suis pas sûr est de savoir s'il est suffisant dans votre cas de l'avoir en tant que MessageProcessor abstrait.

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

29
Jeroen Huinink

Ce qui suit semble également fonctionner, et il est un peu plus court que les autres réponses:

T result = (T)Convert.ChangeType(otherTypeObject, typeof(T));
18
ptrc
Type type = typeof(MessageProcessor<>).MakeGenericType(key);

C'est ce que vous pouvez faire de mieux, mais sans vraiment savoir de quel type il s'agit, vous ne pouvez vraiment pas en faire plus.

EDIT: Je devrais clarifier. J'ai changé de type var en type. Mon point est, maintenant vous pouvez faire quelque chose comme ceci:

object obj = Activator.CreateInstance(type);

obj sera maintenant du type correct, mais comme vous ne savez pas quel type "clé" correspond à la compilation, il n'y a aucun moyen de la lancer et de faire quelque chose d'utile avec elle.

9
BFree

J'avais 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 utiliser Action <> si je ne connais pas T.

SOLUTION:

Implémenter une interface non générique;

public interface IGetGenericTypeInstance
{
    object GenericTypeInstance();
}

Je peux maintenant convertir l'objet en IGetGenericTypeInstance et GenericTypeInstance renverra la propriété sous la forme objet type.

8
Casey Burns

Vous pouvez écrire une méthode qui prend le type en tant que 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 le bon argument générique. Vous pouvez le faire avec 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});
}
8
Daniel Plaisted

S'il vous plaît voir si la solution suivante fonctionne pour vous. L'astuce consiste à définir une interface de processeur de base prenant 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();
    }
}
5
Prakash Buddhiraja

Tu ne peux pas faire ça. Vous pouvez essayer d’exprimer votre problème d’un point de vue plus détaillé (c’est-à-dire que voulez-vous exactement accomplir avec la variable convertie) pour une solution différente.

Vous pourriez aller avec quelque chose comme ça:

 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);
4
Mehrdad Afshari

Ceci n'est tout simplement pas autorisé:

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 de variable.

Vous devriez faire un changement ou quelque chose comme ça:

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
}
...
3
Colin Burnett

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. À l'aide de la réflexion, vous pouvez transmettre l'objet mappé à toute méthode qui l'attend, puis la conversion sera effectuée pour vous. Ainsi, si vous avez une méthode appelée Accepter qui attend un MessageProcessor en tant que paramètre, vous pouvez la trouver et l’appeler de manière dynamique.

1
eulerfx
    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);
            }
        }
    }
1
wazazhang

La réponse de @DanielPlaisted avant marche généralement, mais la méthode générique doit être publique ou il faut utiliser BindingFlags.NonPublic | BindingFlags.Instance! Impossible de l'afficher en tant que commentaire faute de réputation.

0
Peter

J'ai eu du mal à résoudre un problème similaire autour des classes de tables de données plutôt que des messages. Le problème fondamental 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 identique. 

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 finalement fallu mettre en œuvre 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 toute la mise en œuvre en utilisant la méthode générique ci-dessus.

La classe générique qui sera lancée à partir 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 la convertit 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>();
0
JesikaDG