Параметризиране на обща система за събития

Не съм сигурен дали това е задавано преди, но наистина не знаех как да го търся, тъй като не съм сигурен как точно се нарича това нещо / това, което се опитвам да направя...

Имам генерична система за съобщения, базирана на делегати, която използвам в 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);
    }

Проблемът, който имам, е, че кодът в момента е много несух (има много copy paste) и искам да го изсуша, като се надявам да го параметризирам, правейки го по-генеричен.

Ще публикувам съответния код - Моля, имайте предвид, че всъщност не е нужно да разбирате кода в детайли и какво прави, за да отговорите.

Ето един клас, който прави неща зад сцената:

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 таг, защото наистина проблемът не е свързан с единството.   -  person vexe    schedule 06.11.2013
comment

Има точка и запетая точно след цикъла while

while(scan.hasNextLine());
//                       ^

което го прави безкраен цикъл, защото представлява празна инструкция, точно като

while(scan.hasNextLine()){}

така че всъщност никога не влизате

{
    String stringRead = scan.nextLine();
    pw.println(lineNumber + ": " + stringRead);
    lineNumber++;
}

блок и код след него, което означава, че не пишете нищо във вашия файл с резултати и дори не го затваряте. Всичко, което имате, ако е създаден празен файл с

FileOutputStream fos = new FileOutputStream("C:\\Users\\Reid\\Desktop\\rulicny.txt");

Така че премахнете тази точка и запетая.


Между другото можете лесно да забележите този вид грешки, ако оставите кода на вашия IDE формат вместо вас. Eclipse форматира вашия пример за мен като

while (scan.hasNextLine())
    ;
{
    String stringRead = scan.nextLine();
    pw.println(lineNumber + ": " + stringRead);
    lineNumber++;
}

което, както виждате, показва доста добре, че вашият цикъл всъщност изпълнява празен оператор вместо кодов блок.

  -  person vexe    schedule 06.11.2013
comment
The two languages are similar - Да, през 2001 г. беше вярно. Но сега сме 2013 г. C# порасна. java няма. виждането на java код кара един C# разработчик да повърне през 2013 г. Много повече, ако наистина е C# код, написан от java програмист.   -  person Federico Berasategui    schedule 06.11.2013
comment
Е, изглежда, че все пак трябваше да добавя маркера Unity3D - защото навсякъде, където отида, виждам неща за MVVM като Основната цел на инструментариума е да ускори създаването и разработването на MVVM приложения в WPF, Silverlight и в Windows Phone. - Докато използвам Unity3D, MVVM messenger все още ли е полезен за мен? специфично за WPF/Silverlight ли е или може да се използва като цяло във всяка среда? Не искам да прекарвам време в изучаване на това нещо, ако не мога да го използвам.   -  person vexe    schedule 06.11.2013
comment
въпреки че има някои специфични за WPF неща в Messenger изпълнението на MVVM Light, по-голямата част от кода е независим от UI/платформа. Базирам се само на неща като Action<T> и WeakReference, които са дефинирани в mscorlib и не зависят от рамката на потребителския интерфейс или подобни неща. Можете да използвате техните Messenger и да премахнете специфичните за WPF части, като DialogMessage и други подобни.   -  person Federico Berasategui    schedule 06.11.2013
comment
Тъй като има неща, които няма да използвам, не мисля да го използвам. Аз съм много минималистичен, обичам да се ЦЕЛУВАМ и обичам пълен контрол. - Предполагам, че ще трябва да намеря решение на проблема си. 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 в wiki е малко остарял. C# и дори самото Unity имат доста хубава система за съобщения, която вече е на място (стига вашите нужди да не са твърде сложни). Вижте SendMessage и BroadcastMessage.

person S.Richmond    schedule 08.11.2013
comment
Благодаря за отговора, но системата за съобщения на Unity е малко бавна - но понякога е полезна. Но никога не съм го използвал. Вижте това сравнение forum.unity3d. com/threads/ - person vexe; 08.11.2013