Question

How do I pass along an event between classes?

I know that sounds ridiculous (and it is) but I've been stumped on this for the past little while. Search didn't turn up a similar question so I figured I would pose it.

Here are the objects involved:

WinForm -> Speaker -> Tweeter
                   -> Woofer

[Speaker, Tweeter, Woofer] all declare a "SpeakToMe" event that sends a simple string message. The events are declared using the standard pattern:

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 is a simple class inheriting from EventArgs & containing a string property (Message).

On their own, each of these events works fine. E.g., I set a button in the form to create, subscribe, and fire the event for [Speaker, Tweeter, Woofer]. Each reports back properly.

The problem is when Speaker creates a [Tweeter, Woofer] and subscribes to their events.

What I want is for [Tweeter, Woofer] to fire their event, Speaker to consume it and fire it's own event. I thought this should be very straight forward:

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

Stepping through this function (in Speaker), Console.Out.WriteLine works. Continuing to step through OnSpeakToMe, shows that the delegate is null.

Speaker's SpeakToMe event is subscribed to by the form. I understood that this should prevent the event's delegate from being null.

I'm sure this is an easy one, what am I missing?

Btw, in case you're curious as to why I'm looking for this. [Speaker, Tweeter, Woofer] are my demo stand-ins for a really long data processing operation. The form runs several of these concurrently and requires progress updates from each class.

As always, any and all help is greatly appreciated!

Update: Thanks for all of the feedback everyone. I really appreciate the help! I picked up a couple of good tips (@David Basarab & @Brian) and a few different ideas on how to structure things. Again, much appreciated!

Was it helpful?

Solution

If I understood what you wanted in the basic sense. Is to have the Tweeter and Woofer fire an event that the Speaker is subscribed too then fire its own.

Here is my code that has this output

OUTPUT

OnSpeak Message = OnSpeakToMeHander Orginal Message: Fired By Tweeter

OnSpeak Message = OnSpeakToMeHander Orginal Message: 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);
        }
    }
}

OTHER TIPS

Eric Lippert warns against if (SpeakToMe != null) code. While it might not be an issue in your case (i.e. if you never remove events), you should get into the habit of using this instead:

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

In C#6 and higher, consider this terser code instead:

SpeakToMe?.Invoke(e)

The latter approach is suggested on the MSDN

The only way the delegate will be null is if the form is actually NOT subscribed to the event. Did you create a new instance of speaker at some point? If you had subscribed one instance, and then created a new instance using the same variable, the events for the new instance will not be hooked up.

You can use the long form of event declaration to pass the event to an internal object.

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

Rather than passing the event, why not assign an additional handler for the event? You aren't limited to just one method.

//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");
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top