C# Bubbling/Передача события

Как передать событие между классами?

Я знаю, что это звучит смешно (и это так), но последнее время я был в тупике. Поиск не нашел похожего вопроса, поэтому решил его задать.

Вот задействованные объекты:

WinForm -> Speaker -> Tweeter
                   -> Woofer

[Speaker, Tweeter, Woofer] объявляют событие «SpeakToMe», которое отправляет простое строковое сообщение. События объявляются по стандартному шаблону:

public delegate void SpeakToMeHandler(object sender, SpeakToMeEventArgs e);
public event SpeakToMeHandler SpeakToMe;
protected virtual void OnSpeakToMe(string message)
{
   if (SpeakToMe != null) SpeakToMe(this, new SpeakToMeEventArgs(DateTime.Now.ToString() + " - " + message));
}

SpeakToMeEventArgs — это простой класс, наследуемый от EventArgs и содержащий строковое свойство (Message).

Само по себе каждое из этих событий работает нормально. Например, я устанавливаю кнопку в форме для создания, подписки и запуска события для [Динамик, Твитер, НЧ-динамик]. Каждый отчитывается должным образом.

Проблема в том, что Speaker создает [Tweeter, Woofer] и подписывается на их события.

Я хочу, чтобы [Tweeter, Woofer] запускал свое событие, а Speaker потреблял его и запускал собственное событие. Я думал, что это должно быть очень прямолинейно:

void tweeter_SpeakToMe(object sender, SpeakToMeEventArgs e)
{
   Console.Out.WriteLine("the tweeter is speaking: " + e.Message);
   this.OnSpeakToMe("tweeter rockin' out [" + e.Message + "]");
}

Проходя через эту функцию (в динамике), работает Console.Out.WriteLine. Продолжая выполнять OnSpeakToMe, вы увидите, что делегат имеет значение null.

Событие SpeakToMe спикера подписывается формой. Я понял, что это должно препятствовать тому, чтобы делегат события был нулевым.

Я уверен, что это легко, что я упускаю?

Кстати, если вам интересно, почему я это ищу. [Динамик, твитер, низкочастотный динамик] — это мои демо-заменители для действительно длительной операции обработки данных. Форма запускает несколько из них одновременно и требует обновлений хода выполнения от каждого класса.

Как всегда, любая помощь приветствуется!

Обновление: спасибо всем за отзывы. Я очень ценю помощь! Я подобрал пару хороших советов (@David Basarab & @Brian) и несколько разных идей о том, как структурировать вещи. Опять же, очень признателен!


person Mark    schedule 19.06.2009    source источник
comment
Можете ли вы предоставить фрагмент кода того, как ваша форма подписывается на объект Speaker?   -  person LBushkin    schedule 19.06.2009


Ответы (5)


Если я понял, что вы хотели в основном смысле. Нужно, чтобы твитер и низкочастотный динамик запускали событие, на которое подписан динамик, а затем запускали свое собственное.

Вот мой код, который имеет этот вывод

ВЫВОД

Сообщение OnSpeak = Исходное сообщение OnSpeakToMeHander: запущено автором твиттера

Сообщение OnSpeak = Исходное сообщение OnSpeakToMeHander: Fired By Woofer

class Program
{

    static void Main(string[] args)
    {
        Console.Clear();

        try
        {
            Speaker speaker = new Speaker();
            speaker.speakerEvent += new SpeakToMeHandler(Program.OnSpeak);

            // Cause events to be fied
            speaker.Tweeter.CauseEvent();
            speaker.Woofer.CauseEvent();

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: {0}", ex.Message);
            Console.WriteLine("Stacktrace: {0}", ex.StackTrace);
        }
    }

    public static void OnSpeak(object sendere, SpeakToMeEventArgs e)
    {
        Console.WriteLine("OnSpeak Message = {0}", e.Message);
    }

}

public delegate void SpeakToMeHandler(object sender, SpeakToMeEventArgs e);

public class SpeakToMeEventArgs : EventArgs
{
    public string Message { get; set; }
}

public class Speaker
{
    public event SpeakToMeHandler speakerEvent;

    public Tweeter Tweeter { get; set; }
    public Woofer Woofer { get; set; }

    public void OnSpeakToMeHander(object sender, SpeakToMeEventArgs e)
    {
        if (this.speakerEvent != null)
        {
            SpeakToMeEventArgs args = new SpeakToMeEventArgs
                {
                    Message = string.Format("OnSpeakToMeHander Orginal Message: {0}", e.Message)
                };

            this.speakerEvent(this, args);
        }
    }

    public Speaker()
    {
        this.Tweeter = new Tweeter();
        this.Woofer = new Woofer();

        Tweeter.tweeterEvent += new SpeakToMeHandler(this.OnSpeakToMeHander);
        Woofer.wooferEvent += new SpeakToMeHandler(this.OnSpeakToMeHander);
    }
}

public class Tweeter
{
    public event SpeakToMeHandler tweeterEvent;

    public void CauseEvent()
    {
        SpeakToMeEventArgs args = new SpeakToMeEventArgs()
            {
                Message = "Fired By Tweeter"
            };

        if (this.tweeterEvent != null)
        {
            this.tweeterEvent(this, args);
        }
    }
}

public class Woofer
{
    public event SpeakToMeHandler wooferEvent;

    public void CauseEvent()
    {
        SpeakToMeEventArgs args = new SpeakToMeEventArgs()
            {
                Message = "Fired By Woofer"
            };

        if (this.wooferEvent != null)
        {
            this.wooferEvent(this, args);
        }
    }
}
person David Basarab    schedule 19.06.2009
comment
Спасибо за ответ Дэвид. Ваша структура была очень похожа на ту, что была у меня. Основным отличием была унифицированная функция вызова события. Все еще не смог на 100% определить, где была моя ошибка, но ваша структура работает как шарм. Должно быть, я проглядел одну из частей... это был один из тех дней. Спасибо еще раз! - person Mark; 19.06.2009

Эрик Липперт предостерегает от if (SpeakToMe != null) кода . Хотя это может не быть проблемой в вашем случае (т.е. если вы никогда не удаляете события), вы должны привыкнуть использовать это вместо этого:

var tmp = SpeakToMe;
if (tmp!=null) tmp(/*arguments*/);

В C# 6 и выше рассмотрите вместо этого более краткий код:

SpeakToMe?.Invoke(e)

Последний подход предлагается в MSDN.

person Brian    schedule 19.06.2009
comment
А, спасибо за эту ссылку. Я искал это целую вечность. - person Cameron MacFarland; 19.06.2009
comment
Иногда вместо этого я использовал var tmp = SpeakToMe ?? delegate { }; tmp(/*arguments*/);. - person Brian; 05.11.2010

Единственный способ делегата будет нулевым, если форма фактически НЕ подписана на событие. Вы когда-нибудь создавали новый экземпляр динамика? Если вы подписались на один экземпляр, а затем создали новый экземпляр, используя ту же переменную, события для нового экземпляра не будут связаны.

person Jon B    schedule 19.06.2009

Вы можете использовать длинную форму объявления события, чтобы передать событие внутреннему объекту.

public class Speaker
{
    public Speaker()
    {
        this.MyTweeter = new Tweeter();
    }

    public Tweeter MyTweeter { get; private set; }

    public event SpeakToMeHandler SpeakToMe
    {
        add { MyTweeter.SpeakToMe += value; }
        remove { MyTweeter.SpeakToMe -= value; }
    }
}
person Cameron MacFarland    schedule 19.06.2009
comment
Одна из проблем с этим решением заключается в том, что вызывающий абонент может быть удивлен, когда параметр sender не является Speaker, а скорее Tweeter. - person Andreas; 03.11.2010

Вместо того, чтобы передавать событие, почему бы не назначить для него дополнительный обработчик? Вы не ограничены только одним методом.

//Handler class
public class Speaker {
    public delegate void HandleMessage(string message);
    public event HandleMessage OnMessage;
    public void SendMessage(string message) {
        if (OnMessage != null) { OnMessage(message); }
    }
}

//then used like...
Speaker handler = new Speaker();
handler.OnMessage += (message) => { Console.WriteLine("Woofer: {0}", message); };
handler.OnMessage += (message) => { Console.WriteLine("Tweeter: {0}", message); };
handler.SendMessage("Test Message");
person hugoware    schedule 19.06.2009
comment
Как я понял вопрос сигнал идет в обратном направлении? Динамик слушает Твитер и НЧ-динамик, а форма слушает Динамика. Хотя может я неправильно прочитал... - person Fredrik Mörk; 19.06.2009
comment
Возможно, но мне кажется, что вы все еще можете организовать свой код так, чтобы обработчики событий заботились о себе, а не передавали одно событие другому... Хотя не уверен :) - person hugoware; 19.06.2009
comment
@HBoss спасибо за понимание. Определенно интересный подход. К сожалению, Фредрик прав, в данном конкретном случае поток идет в противоположном направлении. - person Mark; 19.06.2009