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".
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With