Is it possible to keep a list of open generic types in one place?
Asked Answered
P

5

5

I'm trying to have a List of open generic types. Is it possible to have something like:

public class MessageProcessor
{
    private IDictionary<Type, IMessageHandler<>> _messageHandlers 
        = new Dictionary<Type, IMessageHandler<>>();

    public void AddHandler<TMessage>(IMessageHandler<TMessage> handler)
    {
        var messageType = typeof(TMessage);
        // Add to dictionary here
    }

    public void Handle<TMessage>(TMessage message)
    {
        // Call the correct handler here.
    }
}

IMessageHandler should have a strongly typed method:

public void Handle(TMessage message) {}

My real example is a little more complex, so I hope I've simplified it correctly here.

The fact is, I'm not interested in what the generic type is of each handler. I just need all of them in one place, and I can easily find the correct handler, if I can get them in that one place.

The private Dictionary will have the Type of the message (TMessage) as key. So I want to be able to do:

// ByteArrayHandler implements IMessageHandler<byte[]>
x.AddHandler(new ByteArrayHandler()) 
// StringHandler implements IMessageHandler<string>
x.AddHandler(new StringHandler()) 

x.Handle("Some message");
x.Handle(new byte[] { 1, 2, 3} );

And have the MessageProcessor call the correct MessageHandler.

Perineuritis answered 16/5, 2014 at 15:2 Comment(5)
I would keep the relationship between message types and message handlers in a config file (app.config or web.config) and user dependency injection or IOC to instantiate and 'handle' when the message is received. unity.codeplex.comMilieu
IDictionary<Type, object> sounds like it would work?Polyhydric
Could you not have IMessageHandler expose a Type property that tells you what it handles rather than making it generic?Argyres
You might want to look at MVVMLight. It implements something very similar with its messenger class. [1]: mvvmlight.net/docBellaude
Your Handle method is generic, but it doesn't really gain you anything because they're only used at runtime.Schnell
U
6

Everybody knows about extension methods. But what about "extension fields"? Of course it is impossible to extend some object with some new fields, but... have you seen ConditionalWeakTable class? Using that we can attach/associate some data to existing objects. The cool feature is that we don't need to explicitly remove the items from that dictionary. The objects are stored as weak references so when the key is collected by GC the key-value pair is automatically removed. Using that we could invent this tricky solution:

public class MessageProcessor
{
    private static class MessageHandlerHolder<TMessage>
    {
        public static readonly ConditionalWeakTable<MessageProcessor, IMessageHandler<TMessage>> MessageHandlers =
            new ConditionalWeakTable<MessageProcessor, IMessageHandler<TMessage>>();
    }

    public void AddHandler<TMessage>(IMessageHandler<TMessage> handler)
    {
        MessageHandlerHolder<TMessage>.MessageHandlers.Add(this, handler);
    }

    public void Handle<TMessage>(TMessage message)
    {
        IMessageHandler<TMessage> handler;
        if (!MessageHandlerHolder<TMessage>.MessageHandlers.TryGetValue(this, out handler))
            throw new InvalidOperationException("...");
        handler.Handle(message);
    }
}

So everything is strongly and statically typed and clients don't need to explicitly remove the handlers to avoid memory leaks.

Unscathed answered 17/5, 2014 at 7:35 Comment(0)
S
1

You have to have it implement another interface, that is not generic.

So:

interface IMessageHandler<T> : IMessageHandler

Then your MessageProcessor class would keep references to IMessageHandler instead.

"Empty" generic types are only used for typeof, as far as I know.

Shull answered 16/5, 2014 at 15:5 Comment(0)
U
1

Downcast seems to be unavoidable. I've a bit generalized this to allow Actions to be added too:

public class MessageProcessor
{
    private readonly IDictionary<Type, Action<object>> _messageHandlers = new Dictionary<Type, Action<object>>();

    public void AddHandler<TMessage>(IMessageHandler<TMessage> handler)
    {
        AddHandler((Action<TMessage>) handler.Handle);
    }

    public void AddHandler<TMessage>(Action<TMessage> handler)
    {
        var messageType = typeof (TMessage);
        _messageHandlers.Add(messageType, msg => handler((TMessage) msg));//Note: downcast
    }

    public void Handle<TMessage>(TMessage message)
    {
        var messageType = typeof (TMessage);
        _messageHandlers[messageType](message);

        //OR (if runtime type should be used):
        _messageHandlers[message.GetType()](message);
    }
}

UPDATE: Of course you can change the type of the messageHandlers to Dictionary<Type, object> and store the IMessageHandler<TMessage> instances directly.

UPDATE 2: Downcasts can only be avoided if everything is static:

public static class MessageProcessor
{
    private static class MessageHandlerHolder<TMessage>
    {
        public static IMessageHandler<TMessage> Handler;
    }

    public static void AddHandler<TMessage>(IMessageHandler<TMessage> handler)
    {
        MessageHandlerHolder<TMessage>.Handler = handler;
    }

    public static void Handle<TMessage>(TMessage message)
    {
        MessageHandlerHolder<TMessage>.Handler.Handle(message);
    }
}
Unscathed answered 16/5, 2014 at 15:33 Comment(0)
S
0

Not based on what you show. IMessageHandler<byte[]> and IMessageHandler<string> are different types, and there's no base type (other than object) to use for a "common" list type.

Some options:

  • Use a Dictionary<Type, object> - the problem is, you'll have to use reflection or cast the values.
  • Use an array of object - you'll need to cast, but you'd need to anyways even if you could store a List<IMessageHandler<>>
  • Use a Dictionary<Type, dynamic> - you won't have to cast, but any errors won't show up until run-time. You would still probably need reflection to determine which handler to call.

If you only support a small set of handlers (you only mention two), it may be cleaner (and safer) to dispatch the calls to the right handler with a generic method and if statements:

public void Handle<T>(T input)
{
   if (typeof(T) == typeof(string))
   {
        stringHandler.Handle(input);
   }
   else if (typeof(T) == typeof(byte[]))
   {
        byteArrayHandler.Handle(input);
   }
   else
   {
        throw new ApplicationException(string.Format("Unsupported type: {0}",typeof(T));
   }
}
Shortwinded answered 16/5, 2014 at 15:5 Comment(0)
A
0

Depending on the fuller requirements another option may be to have your IMessageHandler expose a Type as a property so...

public interface IMessageHandler
{
    Type Handles {get;}
    ...
}

Then in a derived class might look like...

public class StringHandler : IMessageHandler
{
    public Type Handles { get { return typeof(string); } }
    ...     
}

Then your MessageProcessor would look like...

public class MessageProcessor
{
    private IDictionary<Type, IMessageHandler> _messageHandlers = 
        new Dictionary<Type, IMessageHandler>();

    public void AddHandler<TMessage>(IMessageHandler handler)
    {
        //omitted error checking etc for brevity
        _messageHandlers.Add(handler.Handles, handler);
    }

    public void Handle<TMessage>(TMessage message)
    {
        //omitted error checking etc for brevity
        var handler = _messageHandlers[typeof(TMessage)];
    }
}
Argyres answered 16/5, 2014 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.