Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically invoke delegates with known parameter base type?

I am trying to implement my own messaging system for a Unity game. I have a basic version working - a very simplified example of this is as follows:

// Messaging class:

private Dictionary<Type, List<Func<Message, bool>>> listeners; // Set up by some other code.

public void AddListener<T>(Func<Message, bool> listener) where T : Message {
    this.listeners[typeof(T)].Add(listener);
}

public void SendMessage<T>(T message) where T : Message {
    foreach (Func<Message, bool> listener in this.listeners[typeof(T)]) {
        listener(message);
    }
}

// Some other class:

private void Start() {
    messaging.AddListener<MyMessage>(this.MessageHandler); // Subscribe to messages of a certain type.
}

private bool MessageHandler(Message message) { // We receive the message as the Message base type...
    MyMessage message2 = (MyMessage)message; // ...so we have to cast it to MyMessage.
    // Handle the message.
}

This all works fine. What I would like to do now is implement some "magic" to allow the message handler to be called with the actual derived type, like this:

private bool MessageHandler(MyMessage message) {
    // Handle the message.
}

This would mean that the message handling code across thousands of scripts will not need to bother casting the Message object to the correct derived type - it will already be of that type. It would be far more convenient. I feel like this could be possible to achieve somehow using generics, delegates, expression trees, covariance and/or contravariance, but I'm just not getting it!

I've been trying a lot of different things, and feel like I am getting close, but I just can't get there. These are the two partial solutions that I've been able to come up with:

// Messaging class:

private Dictionary<Type, List<Delegate>> listeners; // Delegate instead of Func<Message, bool>.

public void AddListener<T>(Func<T, bool> listener) where T : Message { // Func<T, bool> instead of Func<Message, bool>.
    this.listeners[typeof(T)].Add(listener);
}

public void SendMessage<T>(T message) where T : Message {
    foreach (Delegate listener in this.listeners[typeof(T)]) {
        listener.Method.Invoke(method.Target, new object[] { message }); // Partial solution 1.
        ((Func<T, bool>)listener)(message); // Partial solution 2.
    }
}

Partial solution 1 works fine, but it uses reflection, which isn't really an option considering the performance - this is a game, and the messaging system will be used a lot. Partial solution 2 works, but only as long as the T generic parameter is available. The messaging system will also have a queue of Message objects that it will process, and T will not be available there.

Is there any way to achieve this? I would greatly appreciate any help that anyone could offer!

One final thing to note is that I am using Unity, which uses Mono - .NET 4 is not an option, and as far as I know this rules out using "dynamic".

like image 223
Paul Avatar asked Aug 07 '13 20:08

Paul


1 Answers

If I understood your question correctly, you want to have some common way of storing and invoking message handlers while providing a specific message type to each handler.

One way to do it is to use base-typed inteface handler and generic-typed implementation:

interface IMessageHandler
{
    bool ProcessMessage(Message m);
}

class MessageHandler<T>: IMessageHandler where T:Message
{
    Func<T, bool> handlerDelegate;

    public MessageHandler(Func<T, bool> handlerDelegate)
    {
        this.handlerDelegate = handlerDelegate; 
    }

    public bool ProcessMessage(Message m)
    {
        handlerDelegate((T)m);  
    }
}

Your handler dictionary should hold IMessageHandler as a value, and in your AddListener method create a MessageHandler and add it to handler dictionary. Like this:

private Dictionary<Type, IMessageHandler> listeners;

public void AddListener<T>(Func<T, bool> listener) where T : Message 
{ 
    this.listeners[typeof(T)].Add(new MessageHandler<T>(listener));
}

public void SendMessage(Message message) 
{
    foreach (IMessageHandler listener in this.listeners[message.GetType()]) 
    {
       listener.ProcessMessage(message);
    }
}

That way you can call SendMessage without generic parameter and get typed argument in your actual handler method.

like image 59
alex Avatar answered Oct 01 '22 03:10

alex