Reputation: 63
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".
Upvotes: 6
Views: 1014
Reputation: 2123
Basically you get stuck on the fact that Func<Base, bool> cannot point to bool Method(Derived d). So there's no safe type for storing handles to listeners.
I think one way might be to forgo the common Dictionary of listeners and make the container class generic. So then you'd have message pumps (or atleast containers) for every type. Ex. Something like this?
abstract class BaseMessagePump
{
protected abstract void SendMessage(Message m);
public void AddListener<T>(Func<T, bool> l) where T : Message
{
//check a dictionary / cache
new GenericMessageQueue<T>().listeners.Add(l);
}
}
class GenericMessageQueue<T> : BaseMessagePump where T : Message
{
public List<Func<T, bool>> listeners;
protected override void SendMessage(Message m)
{
listeners[0]((T)m);//the cast here is safe if you do correct cache / type checking in the base add listener
}
}
Upvotes: 0
Reputation: 12654
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.
Upvotes: 5