Simple system events - an unusual approach

It is obvious that static typing allows weed out a large number of errors and helps in writing the code. But sales through 'enum' has its advantage - instead of creating a new message object comes direct transmission parameters in the handler function.
Is it possible to get rid of instantiating objects messages, but do not lose all the advantages of static typing?

Yes, you can. To formulate this in more detail what we want. We need a result similar to the method of sending messages through enum. That is, the method of the form:

events.Dispatch( event ID, event parameters );

The "Event ID" should clearly define the "parameters of the event."

Lets use generics. The first signature that comes to mind, is as follows:

public void Dispatch<TEvent, TSender, TArg>(TEvent e, TSender sender, TArg arg)

It is obvious that she is no better realization through 'enum' since TEvent, TSender TArg and independent. But we know that the event should clearly define its parameters. We describe it as follows:

public void Dispatch<TEvent, TSender, TArg>(IEvent<TEvent, TSender, TArg> e, TSender sender, TArg arg) where TEvent : IEvent<TEvent, TSender, TArg>

First, note that the first argument describes a generic all types of method. Secondly, we have a unique relationship between the event and its parameters. IDE is now able to in the first argument to determine the types of other parameters.

Let us consider the type parameter constraints (design where). It is seen that type TEvent recursively defined by himself. Therefore IEvent define as:

public abstract class IEvent<TEvent, TSender, TArg> where TEvent : IEvent<TEvent, TSender, TArg> { }

Ok, now we can announce your event, along with the types of parameters:

public class StartTurnEvent : IEvent<StartTurnEvent, Player, int> { }

Let us return to the question of sending a message. Now it looks like this:

events.Dispatch(new StartTurnEvent(), player, 123);

We came to the syntax is similar to 'enum' , but instantiating not gone anywhere.
On the other hand, it is obvious that we are not interested in the object and its type. Confirm this by looking inside a method Dispatch:

public void Dispatch<TEvent, TSender, TArg>(IEvent<TEvent, TSender, TArg> e, TSender sender, TArg arg) where TEvent : IEvent<TEvent, TSender, TArg>
{
    foreach (var action in handlers.Get<Action<TSender, TArg>>(typeof(TEvent)))
    {
        action(sender, arg);
    }
}

It can be seen that the first argument is not used at all, then there can be no cause Dispatch instantiation:

events.Dispatch((StartTurnEvent)null, player, 123);

The final touch will get rid of unnecessary brackets and the word 'null' . Make it a constant:

public abstract class IEvent<TEvent, TSender, TArg> where TEvent : IEvent<TEvent, TSender, TArg>
{
    public readonly static TEvent Tag = null;
}

What we got in the end?
1. The syntax similar to sending messages through enum.
2. Static typing and tips IDE.
3. Got rid of instantiation messages before each shipment.



Comparison of Approaches

Announcement of events:

// Method 0: standard messaging system Unity3D
/* Announcement of events is not required */

// Method 1: sending through enum
enum Events { StartTurn }

// Method 2: a typed message system
public class StartTurnMessage : IMessage<Player, int>
{
    public StartTurnMessage(Player player, int value) : base(player, value) { }
}

// Method 2: mixed
public class StartTurnEvent : IEvent<StartTurnEvent, Player, int> { }

Sending messages:

// Method 0: standard messaging system Unity3D
player.SendMessage("OnStartTurn", 123); // No hints from IDE

// Method 1: sending through enum
events.Dispatch(Events.StartTurn, player, 123); // Only hints event name

// Method 2: a typed message system
events.Dispatch(new StartTurnMessage(player, 123));

// Method 3: Mixed
events.Dispatch(StartTurnEvent.Tag, player, 123);

Source

using System;
using System.Collections.Generic;
using System.Linq;
using Collection = System.Collections.Generic.HashSet<object>;

namespace SteamSquad.Gameplay
{
    public class EventBus
    {
        private readonly Container handlers = new Container();

        public void Unsubscribe<TEvent, TSender>(IEvent<TEvent, TSender> tag, Action<TSender> action) where TEvent : IEvent<TEvent, TSender>
        {
            handlers.Remove(typeof(TEvent), action);
        }

        public void Unsubscribe<TEvent, TSender, TArg>(IEvent<TEvent, TSender, TArg> tag, Action<TSender, TArg> action) where TEvent : IEvent<TEvent, TSender, TArg>
        {
            handlers.Remove(typeof(TEvent), action);
        }

        public void Subscribe<TEvent, TSender>(IEvent<TEvent, TSender> tag, Action<TSender> action) where TEvent : IEvent<TEvent, TSender>
        {
            handlers.Add(typeof(TEvent), action);
        }

        public void Subscribe<TEvent, TSender, TArg>(IEvent<TEvent, TSender, TArg> tag, Action<TSender, TArg> action) where TEvent : IEvent<TEvent, TSender, TArg>
        {
            handlers.Add(typeof(TEvent), action);
        }

        public void Dispatch<TEvent, TSender>(IEvent<TEvent, TSender> tag, TSender sender) where TEvent : IEvent<TEvent, TSender>
        {
            foreach (var action in handlers.Get<Action<TSender>>(typeof(TEvent)))
            {
                action(sender);
            }
        }

        public void Dispatch<TEvent, TSender, TArg>(IEvent<TEvent, TSender, TArg> tag, TSender sender, TArg arg) where TEvent : IEvent<TEvent, TSender, TArg>
        {
            foreach (var action in handlers.Get<Action<TSender, TArg>>(typeof(TEvent)))
            {
                action(sender, arg);
            }
        }

        public abstract class IEvent<TEvent, TSender> where TEvent : IEvent<TEvent, TSender>
        {
            public readonly static TEvent Tag = null;
        }

        public abstract class IEvent<TEvent, TSender, TArg> where TEvent : IEvent<TEvent, TSender, TArg>
        {
            public readonly static TEvent Tag = null;
        }

        private class Container
        {
            private readonly Dictionary<Type, Collection> container = new Dictionary<Type, Collection>();

            public void Add(Type type, object action)
            {
                Collection collection;
                if (!container.TryGetValue(type, out collection))
                {
                    container.Add(type, collection = new Collection());
                }
                collection.Add(action);
            }

            public void Remove(Type type, object action)
            {
                container[type].Remove(action);
            }

            public IEnumerable<TAction> Get<TAction>(Type type)
            {
                Collection collection;
                if (container.TryGetValue(type, out collection))
                {
                    return collection.OfType<TAction>();
                }
                return Enumerable.Empty<TAction>();
            }
        }
    }
}

All data posted on the site represents accessible information that can be browsed and downloaded for free from the web.

http://habrahabr.ru/post/245487/

 

User replies

No replies yet