54

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.

  • Following question has a casting scenario avoided using generics - [Refactoring Code to avoid Type Casting](http://stackoverflow.com/questions/21482850/refactoring-code-to-avoid-type-casting) – LCJ Feb 04 '14 at 17:17

13 Answers13

44

The following seems to work as well, and it's a little bit shorter than the other answers:

T result = (T)Convert.ChangeType(otherTypeObject, typeof(T));
ptrc
  • 840
  • 8
  • 16
  • 4
    If there's any specific reason this was voted down, I'd be interested to know why- whether this is bad practice, doesn't work or something else? – ptrc Oct 07 '11 at 13:38
  • 13
    I am guessing this was voted down because this is definitely wrong. Conversion is not casting. http://msdn.microsoft.com/en-us/library/dtb69x08.aspx It only works if you implement IConvertible and as you can see, IConvertible only defines conversions to CLR types: http://msdn.microsoft.com/en-us/library/system.iconvertible.aspx – Shashi Penumarthy Jan 31 '12 at 14:27
  • 13
    Ah, I didn't understand the difference, just knew it worked for me. Thanks for explaining. – ptrc Feb 02 '12 at 14:33
  • 2
    To accommodate the problem with IConvertible, just implement a method that filters your type, like this: public T ConvertObject(object sourceObject) where T : IConvertible { return (T)Convert.ChangeType(sourceObject, typeof(T)); } – Shahzad Qureshi Oct 13 '17 at 17:34
38

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
  • 1,947
  • 3
  • 17
  • 31
  • No, because I need the specific generic instance that has the type safe generic method. Sadly the base class wont do the trick ;) –  Jun 16 '09 at 18:15
  • 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 Jun 16 '09 at 18:25
  • 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. –  Jun 16 '09 at 18:29
  • @Jeroen: That's not correct. You are mapping typeof(string) to its respective MessageProcessor but the message will be of type `Message`, and not `string`. The idea is right-on though. – Mehrdad Afshari Jun 16 '09 at 18:34
  • 1
    @Andrey: Maybe not if you have your messages implement an interface that provides the actual processing. – Jeroen Huinink Jun 16 '09 at 18:34
  • 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. –  Jun 16 '09 at 18:41
  • 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 Jun 16 '09 at 18:54
  • 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. –  Jun 16 '09 at 19:44
  • I have recently implemented something very similar with the mapping of events to commands handled by Autofac Metadata. If you use Autofac for DI, I'd highly recommend looking at the use of its Metadata facilities or Keyed registrations, whichever works best for you. –  Nov 11 '13 at 18:01
13
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
  • 102,548
  • 21
  • 159
  • 201
  • 1
    Won't work. `var` is just syntactic suger resolved at compile time. The type of `type` variable will be `System.Type`. – Mehrdad Afshari Jun 16 '09 at 18:06
  • Yea, I know that. It will be the type of MessageProcessor 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 Jun 16 '09 at 18:08
12

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
  • 1,223
  • 12
  • 15
10

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
  • 16,674
  • 4
  • 44
  • 56
  • 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. –  Jun 16 '09 at 18:20
  • 3
    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 Jun 16 '09 at 18:29
  • Again, Andrej, something to put in your question. – Colin Burnett Jun 16 '09 at 18:46
7

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

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
  • 414,610
  • 91
  • 852
  • 789
  • If this is not possible, is there another way to map a type to a certain generic type? –  Jun 16 '09 at 18:04
  • @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 Jun 16 '09 at 18:07
  • 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. –  Jun 16 '09 at 18:09
  • @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 Jun 16 '09 at 18:14
  • 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. –  Jun 16 '09 at 18:18
  • @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 Jun 16 '09 at 18:21
  • 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. –  Jun 16 '09 at 18:31
3

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
  • 11,150
  • 6
  • 31
  • 40
2

To convert any type object to a generic type T, the trick is to first assign to an object of any higher type then cast that to the generic type.

object temp = otherTypeObject;
T result = (T)temp;
Palash Bansal
  • 708
  • 4
  • 9
1

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
  • 36,769
  • 7
  • 61
  • 83
  • This code might be performance critical, so I want to avoid using reflection or similar approaches. –  Jun 16 '09 at 18:11
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);
            }
        }
    }
wazazhang
  • 11
  • 1
  • 1
    Hello! Welcome to the site! Would you mind describing what your solution does to make it clearer? – tjons Nov 28 '13 at 14:06
0

The answer of @DanielPlaisted before generally works, but the generic method must be public or one must use BindingFlags.NonPublic | BindingFlags.Instance! Couldn't post it as a comment for lack of reputation.

Peter
  • 43
  • 7
0

I struggled to solve a similar problem around data table classes instead of messages. The root issue mentioned above of casting a non-generic version of the class to a derived generic version was the same.

In order to allow injection into a portable class library which did not support database libraries, I introduced a set of interface classes, with the intent that I could pass a type and get a matching generic. It ended up needing to implement a generic method.

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

And this the whole implementation using the generic method above.

The generic class that will be cast from a dictionary.

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

This is the class that stores the generic class and casts it to satisfy the generic method in the 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>;
    }
}

And finally the calling of the interface method.

IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>();
JesikaDG
  • 71
  • 1
  • 3