泛型事件系统参数化

3
我不确定以前是否有人问过这个问题,但我真的不知道如何寻找它,因为我不确定这个东西/我正在尝试做什么被称为什么......

我在Unity3D中使用基于委托的通用消息系统 - 取自这里

[UA Crosslink]

它的使用方式如下:

 // Writing an event listener

    void OnSpeedChanged(float speed)
    {
        this.speed = speed;
    }

// Registering an event listener

    void OnEnable()
    {
        Messenger<float>.AddListener("speed changed", OnSpeedChanged);
    }

// Unregistering an event listener

    void OnDisable()
    {
        Messenger<float>.RemoveListener("speed changed", OnSpeedChanged);
    }

我遇到的问题是,代码目前非常不DRY(有很多复制粘贴),我希望通过参数化使其更加通用,从而使其DRY起来。
我将发布相关代码 - 请注意,您不必详细了解代码及其执行的内容即可回答。
这是一个在后台执行操作的类:
static internal class MessengerInternal
{
    static public Dictionary<string, Delegate> eventTable = new Dictionary<string, Delegate>();
    static public readonly MessengerMode DEFAULT_MODE = MessengerMode.REQUIRE_LISTENER;

    static public void OnListenerAdding(string eventType, Delegate listenerBeingAdded)
    {
        if (!eventTable.ContainsKey(eventType)) {
            eventTable.Add(eventType, null);
        }

        Delegate d = eventTable[eventType];
        if (d != null && d.GetType() != listenerBeingAdded.GetType()) {
            throw new ListenerException(string.Format("Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}", eventType, d.GetType().Name, listenerBeingAdded.GetType().Name));
        }
    }

    static public void OnListenerRemoving(string eventType, Delegate listenerBeingRemoved)
    {
        if (eventTable.ContainsKey(eventType)) {
            Delegate d = eventTable[eventType];

            if (d == null) {
                throw new ListenerException(string.Format("Attempting to remove listener with for event type {0} but current listener is null.", eventType));
            }
            else if (d.GetType() != listenerBeingRemoved.GetType()) {
                throw new ListenerException(string.Format("Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}", eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name));
            }
        }
        else {
            throw new ListenerException(string.Format("Attempting to remove listener for type {0} but Messenger doesn't know about this event type.", eventType));
        }
    }

    static public void OnListenerRemoved(string eventType)
    {
        if (eventTable[eventType] == null) {
            eventTable.Remove(eventType);
        }
    }

    static public void OnBroadcasting(string eventType, MessengerMode mode)
    {
        if (mode == MessengerMode.REQUIRE_LISTENER && !eventTable.ContainsKey(eventType)) {
            throw new BroadcastException(string.Format("Broadcasting message {0} but no listener found.", eventType));
        }
    }
}

现在,我有通用的信使类,可以有一个、两个、三个甚至没有参数 - 因此用户可以选择合适的事件处理程序来订阅事件。
这是不带通用参数的版本:
// No parameters
static public class Messenger {
    private static Dictionary<string, Delegate> eventTable = MessengerInternal.eventTable;

    static public void AddListener(string eventType, Callback handler) {
        MessengerInternal.OnListenerAdding(eventType, handler);
        eventTable[eventType] = (Callback)eventTable[eventType] + handler;
    }

    static public void RemoveListener(string eventType, Callback handler) {
        MessengerInternal.OnListenerRemoving(eventType, handler);   
        eventTable[eventType] = (Callback)eventTable[eventType] - handler;
        MessengerInternal.OnListenerRemoved(eventType);
    }

    static public void Broadcast(string eventType) {
        Broadcast(eventType, MessengerInternal.DEFAULT_MODE);
    }

    static public void Broadcast(string eventType, MessengerMode mode) {
        MessengerInternal.OnBroadcasting(eventType, mode);
        Delegate d;
        if (eventTable.TryGetValue(eventType, out d)) {
            Callback callback = d as Callback;
            if (callback != null) {
                callback();
            } else {
                throw MessengerInternal.CreateBroadcastSignatureException(eventType);
            }
        }
    }
}

这是一个只需要一个参数的版本(我只需复制粘贴并添加一个 T):
// One parameter
static public class Messenger<T> {
    private static Dictionary<string, Delegate> eventTable = MessengerInternal.eventTable;

    static public void AddListener(string eventType, Callback<T> handler) {
        MessengerInternal.OnListenerAdding(eventType, handler);
        eventTable[eventType] = (Callback<T>)eventTable[eventType] + handler;
    }

    static public void RemoveListener(string eventType, Callback<T> handler) {
        MessengerInternal.OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (Callback<T>)eventTable[eventType] - handler;
        MessengerInternal.OnListenerRemoved(eventType);
    }

    static public void Broadcast(string eventType, T arg1) {
        Broadcast(eventType, arg1, MessengerInternal.DEFAULT_MODE);
    }

    static public void Broadcast(string eventType, T arg1, MessengerMode mode) {
        MessengerInternal.OnBroadcasting(eventType, mode);
        Delegate d;
        if (eventTable.TryGetValue(eventType, out d)) {
            Callback<T> callback = d as Callback<T>;
            if (callback != null) {
                callback(arg1);
            } else {
                throw MessengerInternal.CreateBroadcastSignatureException(eventType);
            }
        }
    }
}

作为你可能已经猜到的,接受两个参数的那个我只是复制粘贴了一遍,并添加了另一个通用类型,如<T,U>等。
这是我试图消除的部分-但是我还不知道如何做到。 更准确地说,我正在寻找的是:仅一个Messenger类,但我仍然能够做到:
Messenger<float>.Subscribe("player dead", OnDead);
Messenger<int, bool>.Subscribe("on something", OnSomething);
Messenger<bool, float, MyType>.Subscribe( stuff );

或者,(哪个都无所谓)
Messenger.Subscribe<float> ("player dead", OnDead);

你有个想法...

我该怎么做,怎样编写一个通用的通信程序,当我想添加另一个通用参数时,我不必复制粘贴并编写另一个完整版本,只因为我需要额外的参数?

非常感谢!


谢谢。我之前没有听说过MVVM架构模式。现在我正在研究它。这不是我写的代码,而是从Unity的Wiki上找到的。我不确定这个MVVM架构模式是否适用于Unity。我不想添加Unity3d标签,因为这个问题实际上与Unity无关。 - vexe
1
这两种语言很相似 - 是的,在2001年是这样。但现在是2013年了,C# 已经成熟了,而Java没有。在2013年,看到Java代码会让C#开发人员感到恶心,更何况它是由Java程序员编写的真正的C#代码。 - Federico Berasategui
好吧,看起来我应该无论如何都要添加Unity3D标签 - 因为无论我走到哪里,我都会看到关于MVVM的东西,比如“工具包的主要目的是加速在WPF、Silverlight和Windows Phone中创建和开发MVVM应用程序。”
  • 虽然我正在使用Unity3D,但MVVM信使对我仍然有用吗?它是WPF/Silverlight特定的,还是可以在任何环境中通用?如果我不能使用它,我不想浪费时间学习这个东西。
- vexe
3
否则,祝你好运重新发明轮子。再见。这种态度…… - 我不确定你是不想帮忙还是不能帮忙。我有一个简单(应该是)的问题,我想知道如何解决它。我不想要现成的东西,我想学习。如果你能帮忙解决问题,我会很感激;如果你不能或者不想帮忙,只是因为代码看起来恶心,那么我认为我们不应该继续讨论了,因为已经偏离主题太远了。谢谢。 - vexe
1
@vexe 别着急。也许你应该看一下C#事件,让C#的传教士们开心一些;-) - Kay
显示剩余5条评论
2个回答

1
你有一个信使,但似乎没有发送任何消息!您正在尝试发送没有正确信封的内容。将要发送的值包装在代表实际消息的类中,然后您可以订阅消息类型,其中将包含您尝试发送的所有值。
public class PlayerSpeedChangedMessage {
    public Guid PlayerId { get; set; }
    public int OldSpeed { get; set; }
    public int NewSpeed { get; set; }
}

public class MyMessageHandler {

    public MyMessageHandler() {
        Messenger<PlayerSpeedChangedMessage>.Subscribe(OnDead);
    }

    HandleSpeedChange(PlayerSpeedChangedMessage message) {
        // Do stuff with the message
    }
}

谢谢Mike,这就是我最终采取的方法——我找到了一个可以实现这个功能的系统——类似于C#内置的事件处理系统,但它的酷之处在于对象不必相互知道即可订阅——请看我的评论:http://answers.unity3d.com/questions/570483/parametrize-generic-event-system.html#answer-571131 - vexe

0

我认为对于C#开发者来说,维基上的Message类有点过时了。C#甚至Unity本身已经有一个相当不错的消息传递系统了(只要你的需求不是太复杂)。请查看SendMessageBroadcastMessage


谢谢您的回复,但Unity消息系统有点慢 - 但有时很有用。虽然从未使用过它。请参阅此比较http://forum.unity3d.com/threads/173492-Comparison-of-Unity%28C-%29-Messaging-Events-Systems - vexe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接