8

I want to create a generic mechanism for handling messages in C#. I have a need for this in my small application, so I don't want to use full blown message bus. My requirements are quite simple:

  • I want to have a couple of classes for messages i.e. Message1, Message2. They can inherit from one base class, that's not a problem, but if they don't I don't care. Currently they do inherit from Message.
  • be able to get handler for each and every message class. i.e. if I send Message1, then Message1Handler class should be instantiated. Handlers have to implement IMessageHandler<T> where T is the message class. IMessageHandler is defined as follows:

    interface IMessageHandler<T>
    {
        void Execute(T message);
    }
    

I wrote a simple "Resolver" class:

public static class HandlerRegistry
{
    private static readonly Dictionary<string, Type> _handlers = new Dictionary<string, Type>(); 


    public static void Register<T, T2>() where T2: IMessageHandler<T>
    {
        _handlers.Add(typeof(T).FullName, typeof(T2));

    }

    public static IMessageHandler<T> Resolve<T>(T parameters)
    {
        var type = _handlers[parameters.GetType().FullName];
        return (IMessageHandler<T>) Activator.CreateInstance(type);
    }
}

In this implementation everything is OK, but one part - the cast to IMessageHandler. When I'm trying to use this with a collection of messages this is what happens: the compiler doesn't know at compile time what actual messages are going to be in the collection - it just assumes that they are all subclasses of Message, so it's trying to cast IMessageHandler<ConcreteMessage> to IMessageHandler<Message> and obviously I'm getting an exception with invalid cast. In this case probably contravariance would help, but I'm not able to declare the parameter as out because I have the message in the Execute method parameters.

Does anyone know an elegant solution to this problem? I know I can make it "more runtime" - instead of using generics just declare void Execute(Message m) and in each and every handler start with trying to cast to the type that I'm expecting, but as someone said somewhere - each and every cast that you write undermines the whole point of using a type system.

kubal5003
  • 7,186
  • 8
  • 52
  • 90
  • Related (just read 'message' where it now mentions 'command'): https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 – Maarten Feb 15 '15 at 21:11
  • It is related, but as far as I can tell he never actually touches my problem. He always operates on the ICommandHandler interface that is being injected from the IoC. – kubal5003 Feb 15 '15 at 21:21
  • You dont need generics here... It isn't adding anything.... – AK_ Feb 15 '15 at 21:53
  • I wouldn't write it like this but its a nice reference... http://stackoverflow.com/questions/1477471/design-pattern-for-handling-multiple-message-types – AK_ Feb 15 '15 at 21:56
  • By the way, is this a single handler per message type, or multiple handlers? – AK_ Feb 15 '15 at 21:58
  • @AK_: I guess single handler per message type, or the dictionary would fail when registering the second handler for that type (though I also guess you're asking so you can point that out if there's a desire for multiple handlers per message type). – JohnLBevan Feb 15 '15 at 22:03
  • I just need one handler, but that doesn't really matter. – kubal5003 Feb 15 '15 at 22:04
  • Im asking because usually single handler turns into multiple.... Also, dont use static for the registry... Its less flexible especially for testing – AK_ Feb 15 '15 at 22:06
  • @AK - that solution is precisely what I don't want to do, so I do agree with you - I also wouldn't implement it this way :) – kubal5003 Feb 15 '15 at 22:08
  • @kubal5003 i dont like the if-ologey there... But I think the general direction is fine. That is not using generics here. Why are you trying to avoid a similar solution? – AK_ Feb 15 '15 at 22:13
  • also, do you care if this is configurable? – AK_ Feb 15 '15 at 22:32
  • As @AK_ already wrote, you don't need generics here. They are only offering a false sense of type safety, but in reality, your `Resolve` method always resolves to `Resolve` (since that is the only compile type information you have about that variable), and is effectively equal to `Resolve(Message m)`. The error that the compiler is giving you is perfectly understandable. If `Resolve` returns a `IMessageHandler`, no one guarantees it can handle a certain derived message. You cannot expect to get a compile time generic type for a run-time variable type. – vgru Feb 16 '15 at 08:47
  • I do understand the reason why compiler is giving me this error. You are right that generics give me false sense of type safety, because all those types are going to be devised at runtime. There's however a difference for people that are going to be implementing message handlers - implementing `IHandleMessage` is waaaay nicer than implementing generic `IHandleMessage`. If I'm going to wirte an ugly code I want it to be isolated and controlled instead of scattered everywhere. – kubal5003 Feb 16 '15 at 08:58
  • @kubal5003 there's an issue with your `Resolve` method, its generic, but I guess you won't know the actual type of the message until runtime... – AK_ Feb 16 '15 at 11:28
  • @kubal5003: I am not arguing about `IMessageHandler` vs `IMessageHandler`, of course the interface for the handler should be generic. But your return type for `Resolve` is *always* going to be `IMessageHandler`, and this is the indication that you're not doing this right. You either need to let `Resolve` dispatch the message to the handler and avoid returning it altogether, or go along with this, having to cast in each handler. The former approach is obviously better. I don't see the reason why you need to return the actual handler - or even instantiate handlers inside `Resolve`. – vgru Feb 16 '15 at 13:01
  • @kubal5003: regarding the last remark, instantiating handlers like you're doing now means 1) you are going to have lots of singletons/static fields in order to pass data around (because you have no way of passing any context to these handler constructors), and 2) you don't have a way of attaching multiple handlers to the same message type. Also, making the whole class a singleton (or a static class) will make testing harder. – vgru Feb 16 '15 at 13:07

4 Answers4

2

How about this for a message router:

 class Tester
    {
        public void Go()
        {
            var a = new MessageA();
            var b = new MessageB();
            var c = new MessageC();

            var router = new MessageRouter();
            router.RegisterHandler(new HandlerA());
            router.RegisterHandler(new HandlerB());

            router.Route(a);
            router.Route(b);
            router.Route(c);
        }
    }

    class MessageRouter
    {
       Dictionary<Type, dynamic> m_handlers = new Dictionary<Type,dynamic>();

        public void RegisterHandler<T>(IMessageHandler<T> handler)
        {
            m_handlers.Add(typeof(T), handler);
        }

        public void Route(dynamic message)
        {
            var messageType = message.GetType();
            if (m_handlers.ContainsKey(messageType))
            {
                m_handlers[messageType].Handle(message);
            }
            else
            {
                foreach (var pair in m_handlers)
                {
                    if(pair.Key.IsAssignableFrom(messageType))
                    {
                        pair.Value.Handle(message);
                    }
                }
            }
        }

    }

    class MessageA
    {
        public virtual string A { get { return "A"; } }
    }
    class MessageB
    {
        public  string B { get { return "B"; } }
    }

    class MessageC :MessageA
    {
        public  override string A {  get { return "C"; } }
    }
    interface IMessageHandler<T>
    {
        void Handle(T message);
    }
    class HandlerA : IMessageHandler<MessageA>
    {

        public void Handle(MessageA message)
        {
            Console.WriteLine(message.A);
        }
    }
    class HandlerB : IMessageHandler<MessageB>
    {

        public void Handle(MessageB message)
        {
            Console.WriteLine(message.B);
        }
    }
AK_
  • 7,981
  • 7
  • 46
  • 78
  • It's a nice solution. You're not pretending anything here, just use dynamic invocation to do the job. I also like the fact that it does not require reflection. What I don't like is that the registration process is quite complicated and not extensible. – kubal5003 Feb 16 '15 at 00:06
2

Option 1

If you do not care using reflection. You can add a Execute method to your HandlerRegistry instead of returning the handler back to the caller:

public static void Execute<T>(T parameters)
{
    var type = _handlers[parameters.GetType().FullName];
    var handler = Activator.CreateInstance(type);
    type.GetMethod("Execute", new[] { parameters.GetType() })
        .Invoke(handler, new object[] { parameters });
}

Option 2

If you do not care that one message handler can only subscribe to one message. We can take advantage of the Explicit Interface Implementation feature of C#:

// NOTE: This interface is not generic
public interface IMessageHandler
{
    void Execute(object message);
}

public abstract class MessageHandler<T> : IMessageHandler
{
    public abstract void Execute(T message);

    // NOTE: Here we explicitly implement the IMessageHandler
    void IMessageHandler.Execute(object message)
    {
        Execute((T)message);
    }
}

Now your resolve method can change to:

public static IMessageHandler Resolve<T>(T parameters)
{
    var type = _handlers[parameters.GetType().FullName];
    return (IMessageHandler)Activator.CreateInstance(type);
}

By the way, personally I would prefer to pass in a Type instead of the message instance.

Then make your handlers inherit from the generic abstract MessageHandler<T> instead of implementing IMessageHandler:

public class HandlerA : MessageHandler<MessageA>
{
    public override void Execute(MessageA message)
    {
        Console.WriteLine("Message A");
    }
}

public class HandlerB : MessageHandler<MessageB>
{
    public override void Execute(MessageB message)
    {
        Console.WriteLine("Message B");
    }
}
Mouhong Lin
  • 4,402
  • 4
  • 33
  • 48
  • The first approach is the most reasonable one (execute the handler inside the method), but there is also no need for generics here. – vgru Feb 16 '15 at 08:49
  • I also prefer the first approach. Reflection is not that bad most of time :) – Mouhong Lin Feb 16 '15 at 08:52
1

How about taking a slightly different approach: Instead of registering the type of a handler, why not register the actual handler instance which will process the message? This gives you much greater flexibility in instantiation of the handler, and removes any type ambiguities.

The idea is to be able to do this:

// have several handler classes
class FooMessageHandler : IMessageHandler<Foo>
{  }

class BarMessageHandler : IMessageHandler<Bar>
{  }

// have them instantiated - allows you to pass much more context
// than Activator.CreateInstance is able to do    
var fooMessageHandler = new FooMessageHandler(various params);
var barMessageHandler = new BarMessageHandler(various params);

// register actual instances    
HandlerRegistry.Register<Foo>(fooMessageHandler);
HandlerRegistry.Register<Bar>(barMessageHandler);

// handler registry will simply dispatch the message to
// one of the handlers        
HandlerRegistry.Dispatch(someFooMessage);

Not only that, but the approach allows you to register multiple handlers for each message type:

// these will all get called when a Foo message is received    
HandlerRegistry.Register<Foo>(fooMessageHandler);
HandlerRegistry.Register<Foo>(someOtherFooHandler);
HandlerRegistry.Register<Foo>(yetAnotherFooHandler);
vgru
  • 49,838
  • 16
  • 120
  • 201
0

What if you inherit all you messages from common abstract MessageBase and instead of making message handler interface IMessageHandler<T> generic, put constraint on Execute method itself?

Namely void Execute<T>(T message) where T : MessageBase.

This way you get the desired functionality and your message handler resolver, HandlerRegistry, needs only a minor tweak. Just change return type & constraint from IMessageHandler<T> to IMessageHandler.

Below are slightly modified MessageBase, IMessageHandler and HandlerRegistry.

(Related dotnetfiddle here https://dotnetfiddle.net/e6M1UA)

// Message
public abstract class MessageBase
{
    public virtual void Action() // ...for examples sake
    {
        Console.WriteLine(GetType().Name);
    }
}

// Message handler
public interface IMessageHandler
{
    void Execute<T>(T message) where T : MessageBase;
}

// Resolver
public static class HandlerRegistry
{
    private static readonly Dictionary<string, Type> Handlers = 
        new Dictionary<string, Type>();

    public static void Register<T, T2>() where T2 : IMessageHandler
    {
        Handlers.Add(typeof(T).FullName, typeof(T2));
    }

    public static IMessageHandler Resolve<T>(T parameters)
    {
        var type = Handlers[parameters.GetType().FullName];
        return (IMessageHandler)Activator.CreateInstance(type);
    }
}

Now, if you test it using e.g. following implementations

public class Message1 : MessageBase
{}

public class Message2 : MessageBase
{
    public override void Action()
    {
        Console.Write(@"Overriding ");
        base.Action();
    }
}

public class Message1Handler : IMessageHandler
{
    public void Execute<T>(T message) where T : MessageBase
    {
        Console.Write(@"MessageHandler1 > ");
        message.Action();
    }
}

public class Message2Handler : IMessageHandler
{
    public void Execute<T>(T message) where T : MessageBase
    {
        Console.Write(@"MessageHandler2 > ");
        message.Action();
        Console.WriteLine(@"...and then some");
    }
}

With this block of code

HandlerRegistry.Register<Message1, Message1Handler>();
HandlerRegistry.Register<Message2, Message2Handler>();

var messages = new List<MessageBase>()
    {
        new Message1(),
        new Message2()
    };

foreach (var message in messages)
{
    var handler = HandlerRegistry.Resolve(message);
    handler.Execute(message);               
}

You'll end up with console log

MessageHandler1 > Message1
MessageHandler2 > Overriding Message2
...and then some

Mikko Viitala
  • 8,344
  • 4
  • 37
  • 62