如何动态调用具有已知参数基类型的委托?

时间:2013-08-07 20:29:39

标签: c# generics mono unity3d messaging

我正在尝试为Unity游戏实现自己的消息传递系统。我有一个基本版本 - 一个非常简单的例子如下:

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

一切正常。我现在要做的是实现一些“魔术”,以允许使用实际的派生类型调用消息处理程序,如下所示:

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

这意味着跨数千个脚本的消息处理代码不需要费心将Message对象强制转换为正确的派生类型 - 它已经是那种类型。它会更方便。我觉得这可能有可能以某种方式使用泛型,代表,表达树,协方差和/或逆变,但我只是没有得到它!

我一直在尝试很多不同的事情,感觉我越来越接近,但我无法到达那里。这些是我能够提出的两个部分解决方案:

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

部分解决方案1工作正常,但它使用反射,考虑到性能,这不是一个真正的选择 - 这是一个游戏,消息系统将被大量使用。部分解决方案2有效,但只有T通用参数可用时才有效。消息传递系统还将有一个它将处理的Message对象队列,并且T将不会在那里可用。

有没有办法实现这个目标?我非常感谢任何人都可以提供的任何帮助!

最后要注意的是我使用Unity,它使用Mono - .NET 4不是一个选项,据我所知,这使用“动态”排除。

2 个答案:

答案 0 :(得分:5)

如果我正确理解了您的问题,您希望有一些常用的方法来存储和调用消息处理程序,同时为每个处理程序提供特定的消息类型。

一种方法是使用基类型接口处理程序和泛型类型实现:

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);  
    }
}

您的处理程序字典应该将IMessageHandler作为值保存,并在AddListener方法中创建MessageHandler并将其添加到处理程序字典中。像这样:

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);
    }
}

通过这种方式,您可以在没有泛型参数的情况下调用SendMessage,并在实际的处理程序方法中获取类型参数。

答案 1 :(得分:0)

基本上你会被Func&lt; Base,bool&gt;这个事实所困扰。不能指向布尔法(Derived d)。因此,没有安全的类型可以将句柄存储到监听器。

我认为一种方法可能是放弃常见的侦听器字典并使容器类具有通用性。那么你就可以为每种类型提供消息泵(或至少容器)。防爆。像这样的东西?

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