How to dynamically call delegates with a known database type?

I am trying to implement my own messaging system for a Unity game. I have a basic version - a very simplified example:

// 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. } 

All of this works great. What I would like to do now is to implement some β€œmagic” that allow calling a message handler with an actual derived type, for example:

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

This would mean that the message processing code in thousands of scripts should not bother to pass the Message object to the correct derived type - it will already be of this type. That would be much more convenient. I feel that I could somehow make use of generics, delegates, expression trees, covariance and / or contravariance, but I just don't get it!

I tried a lot of different things and feel like getting close, but I just can't get there. These are two partial solutions that I could 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 is not really an option, given the performance - it's a game, and the messaging system will be used a lot. Partial Solution 2 works, but only as long as the general parameter T 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 really appreciate any help anyone could offer!

Finally, I want to note that I use Unity, which uses Mono-.NET 4, is not an option, and as far as I know, this eliminates the use of "dynamic".

+6
source share
2 answers

If I understand your question correctly, you want to have some common way to store and call message handlers, while simultaneously providing a specific message type to each handler.

One way to do this is to use an inteface handler with a base type and a typically 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); } } 

The manipulator dictionary must contain the IMessageHandler value as the value, and in the AddListener method create a MessageHandler and add it to the 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); } } 

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

+5
source

You are mostly stuck in that Func <Base, bool> cannot point to the bool Method (Derived d). Thus, there is no safe type for storing pens to listeners.

I think that one of the ways could be to abandon the general Dictionary of listeners and create a universal container class. This way you will have message pumps (or at least containers) for each type. Ex. Something like that?

 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 } } 
0
source

Source: https://habr.com/ru/post/951255/


All Articles