Параметризация общей системы событий

Я не уверен, что об этом спрашивали раньше, но я действительно не знал, как его искать, так как я не совсем уверен, как точно называется эта вещь/то, что я пытаюсь сделать...

У меня есть общая система обмена сообщениями на основе делегатов, которую я использую в Unity3D. Взято из здесь.

[Перекрестная ссылка UA]

Он используется следующим образом:

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

Проблема, с которой я сталкиваюсь, заключается в том, что код в настоящее время очень не СУХОЙ (много копий и вставок), и я хочу СУХОЙ его, надеюсь, параметризировав его, сделав его более общим.

Я опубликую соответствующий код. Обратите внимание, что вам не обязательно подробно разбираться в коде и в том, что он делает, чтобы ответить.

Вот класс, который делает что-то за сценой:

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

Вы поняли идею...

Как я могу это сделать, как я могу написать общий мессенджер, чтобы, когда я хочу добавить еще один общий аргумент, мне не нужно было копировать-вставлять и писать совершенно другую версию, просто потому, что мне нужен дополнительный аргумент?

Большое спасибо!


person vexe    schedule 06.11.2013    source источник
comment
Спасибо. Я не слышал о MVVM. Я смотрю на это сейчас. Это не мой код, это из вики Unity. Я не уверен, что эта штука с MVVM подходит для Unity. Я не хотел добавлять тег Unity3d, потому что на самом деле проблема не связана с Unity.   -  person vexe    schedule 06.11.2013
comment
@HighCore, кстати, в чем проблема, если код C# выглядит слишком java? Эти два языка во многом похожи. Что в этом отвратительного?   -  person vexe    schedule 06.11.2013
comment
The two languages are similar - Да, в 2001 году это было правдой. Но сейчас 2013 год. С# вырос. у джавы нет. В 2013 году у разработчика C# возникает рвота при виде java-кода. Гораздо больше, если это действительно код C#, написанный java-программистом.   -  person Federico Berasategui    schedule 06.11.2013
comment
Что ж, похоже, что я все равно должен был добавить тег Unity3D — потому что везде, куда бы я ни пошел, я вижу такие вещи о MVVM, как Основная цель набора инструментов — ускорить создание и разработку приложений MVVM в WPF, Silverlight и в Windows Phone. - Пока я использую Unity3D, полезен ли мне мессенджер MVVM? это специфично для WPF/Silverlight или может использоваться вообще в любой среде? Я не хочу тратить время на изучение этой штуки, если я не могу ею пользоваться.   -  person vexe    schedule 06.11.2013
comment
хотя в Messenger реализации MVVM Light есть некоторые особенности WPF, большая часть кода не зависит от пользовательского интерфейса/платформы. I основан только на таких вещах, как Action<T> и WeakReference, которые определены в mscorlib и не зависят от фреймворка пользовательского интерфейса или подобных вещей. Вы можете использовать их Messenger и удалить определенные части WPF, такие как DialogMessage и тому подобное.   -  person Federico Berasategui    schedule 06.11.2013
comment
Поскольку в нем есть вещи, которые я не собираюсь использовать, я не думаю, что буду его использовать. Я очень минималистичный, люблю KISS и полный контроль. - Думаю, мне придется найти решение моей проблемы. Make your methods generic - Нравится Broadcast<T>, Broadcast<T, U>, Broadcast<T, etc>? та же проблема. Или вы имели в виду что-то другое? (Кстати, вы не изобретаете велосипед, если вы делаете телевизор, чтобы узнать, как он сделан. Но вы делаете это, чтобы смотреть телевизор)   -  person vexe    schedule 06.11.2013
comment
извините, но ваше утверждение не имеет смысла. Если вам нужна простая вещь, загрузите исходный код и возьмите соответствующие части, которые действительно похожи на ваш код, но написаны на правильном C #, а не на ужасной java. И если вы хотите получить полный контроль, опять же, возьмите исходный код, возьмите соответствующие части и измените из них все, что хотите. В противном случае удачи в изобретении велосипеда. до встречи.   -  person Federico Berasategui    schedule 06.11.2013
comment
Otherwise, good luck reinventing the wheel. see ya такое отношение....... - Не уверен, что ты не хочешь помочь или не можешь. У меня есть простая (должна быть) проблема, и я хочу знать, как ее решить. Мне не нужны готовые вещи. Я хочу учиться. Если вы можете помочь решить проблему, я был бы признателен, если вы не можете / не хотите, просто потому, что код выглядит отвратительно, тогда я действительно думаю, что мы не должны принимать эти комментарии. любая дальнейшая причина, по которой это становится длинным и не по теме. Спасибо.   -  person vexe    schedule 06.11.2013
comment
@vexe успокойся. Возможно, вам стоит взглянуть на события C# чтобы порадовать проповедников C# ;-)   -  person Kay    schedule 06.11.2013
comment
Должен согласиться с @Kay. Может быть, я был недостаточно внимателен, но я не увидел здесь ничего такого, что события, уже встроенные в язык, не могли бы решить.   -  person Max Yankov    schedule 07.11.2013


Ответы (2)


У вас есть мессенджер, но вы не отправляете никаких сообщений! Вы пытаетесь отправить содержимое без надлежащего конверта. Оберните значения, которые вы хотите отправить, в класс, который представляет ваше фактическое сообщение, и затем вы можете подписаться на тип сообщения, которое будет содержать все значения, которые вы пытались отправить.

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
    }
}
person Mike    schedule 08.11.2013
comment
Спасибо, Майк, это то, что я на самом деле сделал - я нашел систему, которая делает это - похожую на встроенную систему обработки событий в C #, но классная вещь в том, что объекты не должны знать друг о друге. подписаться — см. мой комментарий здесь answers.unity3d.com/questions/570483/ - person vexe; 08.11.2013

Я думаю, что для разработчиков C# этот класс Message в вики немного устарел. В C# и даже в самом Unity уже есть довольно хорошая система обмена сообщениями (если ваши потребности не слишком сложны). Ознакомьтесь с SendMessage и BroadcastMessage.

person S.Richmond    schedule 08.11.2013
comment
Спасибо за ваш ответ, но система обмена сообщениями Unity немного медленная, но иногда это полезно. Хотя никогда им не пользовался. См. это сравнение forum.unity3d. ком/потоки/ - person vexe; 08.11.2013