Ryan Peschel
Ryan Peschel

Reputation: 11828

Trying to design a small message handler class to simulate C# events. What are the disadvantages to this approach?

Here is my code:

static class MessageHandler<T> where T : Message
{
    public delegate void MessageDelegate(T m);

    private static MessageDelegate messageHandlers;

    public static void Publish(T message)
    {
        messageHandlers(message);
    }

    public static void Subscribe(MessageDelegate messageHandler)
    {
        messageHandlers += messageHandler;
    }
}

Message is just an empty class that other classes can inherit from. Most Message derivations are simple objects with relevant properties (this is for a game so there might be a PlayerDamagedMessage with the damage and assailant as the properties.)

Basically, in the Player class when they are attacked they would publish a PlayerDamagedMessage and then other classes that want to know about that can subscribe and then receive the relevant details when it occurs.

The reason why this works with more than one message is because the way generic classes work in C#. By this I mean that there will be a copy of the delegate for each different generic type used under the covers.

I actually figured this out by accident while playing around and am excited because it really simplified my code and looks almost like a design pattern at this point.

I'm posting here to ask about the potential downsides for using this kind of approach. Is there a limit to the amount of generic delegates under the covers? How well would something like this scale?

Also, is there any way to have some sort of generic type-inference so that the statements don't have to be this long?

MessageHandler<MessagePlayerDamaged>.Publish(new MessagePlayerDamaged(this, this));

Upvotes: 4

Views: 2054

Answers (4)

MicBig
MicBig

Reputation: 741

I found a way to do it using generics, I added a private Registry class which stores the static delegate:

class Message
{
}

class MessageHandler
{
    public static void Publish<T>(T message) where T : Message
    {
        Registry<T>.action(message);
    }

    public static void Subscribe<T>(Action<T> h) where T : Message
    {
        Registry<T>.action += h;
    }

    private class Registry<T> where T : Message
    {
        public static Action<T> action;
    }
}

This way you don't have to add the type arguments:

class IntMessage : Message
{
    public int Value = 100;
}

class StringMessage : Message
{
    public string Value = "a string";
}

static void Main(string[] args)
{
    MessageHandler.Subscribe((StringMessage m) => Console.WriteLine("String : " + m.Value));
    MessageHandler.Subscribe((StringMessage m) => Console.WriteLine("2nd String : " + m.Value));
    MessageHandler.Subscribe((IntMessage m) => Console.WriteLine("Int : " + m.Value));
    MessageHandler.Subscribe((IntMessage m) => Console.WriteLine("2nd Int : " + m.Value));

    MessageHandler.Publish(new IntMessage());
    MessageHandler.Publish(new StringMessage());
}

Upvotes: 0

mixel
mixel

Reputation: 25856

There is a solution I use in my projects.

    public class MessageDispatcher {
        private readonly Dictionary<Type, MulticastDelegate> registeredHandlers = new Dictionary<Type, MulticastDelegate>();

        private delegate void MessageActionDelegate<in T>(T message);

        public void Register<T>(Action<T> action) {
            Type messageType = typeof (T);
            if (registeredHandlers.ContainsKey(messageType)) {
                var messageDelegate = (MessageActionDelegate<T>) registeredHandlers[messageType];
                registeredHandlers[messageType] = messageDelegate + new MessageActionDelegate<T>(action);
            }
            else {
                registeredHandlers.Add(messageType, new MessageActionDelegate<T>(action));
            }

        }

        public void Deregister<T>() {
            Type messageType = typeof (T);
            if (registeredHandlers.ContainsKey(messageType)) {
                registeredHandlers.Remove(messageType);
            }
        }

        public void DeregisterAll() {
            registeredHandlers.Clear();
        }

        public void Send<T>(T message) {
            Type messageType = typeof (T);
            if (!registeredHandlers.ContainsKey(messageType)) return;

            ((MessageActionDelegate<T>) registeredHandlers[messageType])(message);
        }
    }

And test example:

    private static void Main(string[] args) {
        var messenger = new MessageDispatcher();
        messenger.Register<Message>(m => Console.WriteLine(m.Text));
        messenger.Send(new Message() { Text = "Good morning, sir."});
        messenger.Register<Message>(m => Console.WriteLine(m.Text + " It's nice weather today."));
        messenger.Register<Notification>(n => Console.WriteLine(n.Text));
        messenger.Send(new Message() { Text = "How do you feel? "});
        messenger.Send(new Notification() { Text = "Cup of tea, sir?" });
        messenger.Deregister<Message>();
        messenger.Send(new Message() { Text = "Good bye" });
        Console.ReadLine();
    }

    public class Message {
        public string Text { get; set; }
    }

    public class Notification {
        public string Text { get; set; }
    }

You can make MessageDispatcher a singleton. And if your application is multi-threaded then you need to think about thread-safety.

Upvotes: 1

M.Stramm
M.Stramm

Reputation: 1309

The main differences to C# events:

  • Your messages are identified by their Arguments-Class which makes it impossible to have two different events serving alike purposes (think of MouseUp, MouseDown, MouseMoved, etc.)
  • Your messages are coupled to a static context, not to objects, which makes it hard to register for events from say player1 and not player2
  • And your messages can be invoked from anywhere at all, whereas event invocations are always private to the class/object owning that event

Upvotes: 1

armen.shimoon
armen.shimoon

Reputation: 6401

I've actually used a very similar pattern with much success. One further step I took was encapsulating the actual message handlers within a MessageHandlerRegistry to allow for cleaner syntax. Here is your example modified:

Message.cs

public class Message
{

}

MessageHandler.cs

public class MessageHandler<T> where T : Message
{
    private Action<T> messageHandlers;

    public void Publish(T message)
    {
        messageHandlers(message);
    }

    public void Subscribe(Action<T> messageHandler)
    {
        messageHandlers = (Action<T>) Delegate.Combine(messageHandlers, messageHandler);
    }
}

MessageHandlerRegistry.cs

public static class MessageHandlerRegistry
{
    private static readonly IDictionary<Type, object> _handlers = new Dictionary<Type, object>();

    public static void Publish<T>(T m) where T : Message
    {
        if (_handlers.ContainsKey(typeof (T)))
        {
            ((MessageHandler<T>) _handlers[typeof (T)]).Publish(m);
        }
    }

    public static void Subscribe<T>(Action<T> messageHandler) where T : Message
    {
        if (!_handlers.ContainsKey(typeof (T)))
        {
            _handlers[typeof (T)] = new MessageHandler<T>();
        }
        ((MessageHandler<T>) _handlers[typeof (T)]).Subscribe(messageHandler);
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        MessageHandlerRegistry.Subscribe((Message m) => Console.WriteLine("Message received."));
        MessageHandlerRegistry.Publish(new Message());
    }
}

The only downside I have seen to this is being overly-loosly coupled, and in some situations where it would make more sense to use a traditional event based approach, sometimes it is easier to just publish a message.

Upvotes: 1

Related Questions