tags:

views:

5268

answers:

8

I have a Dictionary to map a certain type to a certain generic object for that type. For example:

typeof(LoginMessage) maps to MessageProcessor<LoginMessage>

Now the problem is to retrieve this generic object at runtime from the Dictionary. Or to be more specific: To cast the retrieved object to the specific generic type.

I need it to work something like this:

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

Hope there is a easy solution to this.

Edit: I do not want to use Ifs and switches. Due to performance issues I cannot use reflection of some sort either.

+2  A: 

You can't do that. You could try telling your problem from a more high level point of view (i.e. what exactly do you want to accomplish with the casted variable) for a different solution.

You could go with something like this:

 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);
Mehrdad Afshari
If this is not possible, is there another way to map a type to a certain generic type?
@Andrej: "map" is a bit vague here. Could you try telling us what you'd want to do with the casted variable? As the type is unknown at compile time, you can't call methods on in or so, unless you hardcode different `key` types...
Mehrdad Afshari
I have certain Messages (LoginMessage, SayMessage,...). Each Message has to be processed by a specific MessageProcessor that has a Method like "ProcessMessage(MessageSource source, MessageType message)".I could of course use the base class of the Messages instead of the generic approach, but I want this to be type safe so that a specific Processor can only process the message, that he is supposed to.
@Andrej: The base class approach can still be "type-safe" but the type-safety won't be enforced at compile-time. There's no way you could do that at compile time as the type is not known at compile-time anyway. I guess that's your best bet.
Mehrdad Afshari
Could you elaborate that? I dont know how it would be possible to not pass say a LoginMessage to a SayMessageProcessor. Thats what I am trying to avoid.
@Andrej: It won't be possible to prevent that all at compile time (unless you manually hardcode types, which is not a good approach) but it is possible to throw an exception in case that happens. By the way, is there a single message processor for each message type? If that's the case, you could use a virtual method on the message itself to call the respective message processor method.
Mehrdad Afshari
Yes there is a Processor for each Message Type.Somehow I dont like type checking at runtime, but it seems to be the only way to go here.
+1  A: 
Type type = typeof(MessageProcessor<>).MakeGenericType(key);

That's the best you can do, however without actually knowing what type it is, there's really not much more you can do with it.

EDIT: I should clarify. I changed from var type to Type type. My point is, now you can do something like this:

object obj = Activator.CreateInstance(type);

obj will now be the correct type, but since you don't know what type "key" is at compile time, there's no way to cast it and do anything useful with it.

BFree
Won't work. `var` is just syntactic suger resolved at compile time. The type of `type` variable will be `System.Type`.
Mehrdad Afshari
Yea, I know that. It will be the type of MessageProcessor<key> which you can then use with Activator.CreateInstance(). My point was though, that at that point, you can't do much with it because you don't know what type "key" is.
BFree
A: 

As mentioned, you cannot cast it directly. One possible solution is to have those generic types inherit from a non-generic interface, in which case you can still invoke methods on it without reflection. Using reflection, you can pass the mapped object to any method expecting it, then the cast will be performed for you. So if you have a method called Accept expecting a MessageProcessor as a parameter, then you can find it and invoke it dynamically.

eulerfx
This code might be performance critical, so I want to avoid using reflection or similar approaches.
A: 

This is simply not allowed:

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

You cannot get a generic type as a variable value.

You'd have to do a switch or something:

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
}
...
Colin Burnett
I use this structure to exactly avoid Ifs and switches. So this is a no go.
Then put that in your question.
Colin Burnett
+4  A: 

Does this work for you?

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

This gives you the correct object. The only thing I am not sure about is whether it is enough in your case to have it as an abstract MessageProcessor.

Edit: I added an IMessage interface. The actual processing code should now become part of the different message classes that should all implement this interface.

Jeroen Huinink
No, because I need the specific generic instance that has the type safe generic method. Sadly the base class wont do the trick ;)
I added an example of how you can add an abstract method to the abstract class and override it in the concrete class.
Jeroen Huinink
I hoped to avoid type checking at runtime but as everyone said, it seems unavoidable. ;)So I will have to go with this approach. Thanks.
@Jeroen: That's not correct. You are mapping typeof(string) to its respective MessageProcessor but the message will be of type `Message<string>`, and not `string`. The idea is right-on though.
Mehrdad Afshari
@Andrey: Maybe not if you have your messages implement an interface that provides the actual processing.
Jeroen Huinink
I used simple runtime type checking to ensure that the message type is correct. I cant do the processing in the message classes, because they are pretty much data only objects, that have to be serialized etc. Otherwise letting the message do its own processing would be the solution.
Maybe I do not exactly understand what goes on with serialization, but why would it stop you from adding code to a class. It will only serialize the data and not the code, wouldn't it?
Jeroen Huinink
Yes, but the Processor has to have access to various business classes and network stuff to actually invoke changes in the application.I think this is no task for a class that simply holds data.
+1  A: 

You can write a method that takes the type as a generic parameter:

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

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

Then you need a way to call the method with the correct generic argument. You can do this with reflection:

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});
}
Daniel Plaisted
As mentioned, due to performance issues I want to avoid using reflection. Those methods may be called hundrets of times in very short timespans and still need to perform very fast.
To optimize this, you could set it up so that the reflection is only done once per message type. You can create a delegate that calls the generic method and store it in a dictionary, so the next time the type comes up it just calls the delegate. The easiest way to do this is probably by compiling a lambda expression.
Daniel Plaisted
Again, Andrej, something to put in your question.
Colin Burnett
A: 

Please see if following solution works for you. The trick is to define a base processor interface which takes a base type of message.

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();
    }
}
Prakash Buddhiraja
A: 

I had a similar problem. I have a class;

Action<T>

which has a property of type T.

How do I get the property when I don't know T? I can't cast to Action<> unless I know T.

SOLUTION:

Implement a non-generic interface;

public interface IGetGenericTypeInstance
{
    object GenericTypeInstance();
}

Now I can cast the object to IGetGenericTypeInstance and GenericTypeInstance will return the property as type object.

Casey Burns