How to dynamically invoke delegates with known parameter base type?
c#, generics, messaging, mono, unity-game-engine
Solution
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.
Problem
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".