xyz
xyz

Reputation: 27837

Why must someone be subscribed for an event to occur?

Some text before the code so that the question summary isn't mangled.

class Tree
{
    public event EventHandler MadeSound;

    public void Fall() { MadeSound(this, new EventArgs()); }

    static void Main(string[] args)
    {
        Tree oaky = new Tree();
        oaky.Fall();
    }
}

I haven't used events much in C#, but the fact that this would cause a NullRefEx seems weird. The EventHandler reference is considered null because it currently has no subsribers - but that doesn't mean that the event hasn't occurred, does it?

EventHandlers are differentiated from standard delegates by the event keyword. Why didn't the language designers set them up to fire silently in to the void when they have no subscribers? (I gather you can do this manually by explicitly adding an empty delegate).

Upvotes: 12

Views: 758

Answers (9)

Taylor Leese
Taylor Leese

Reputation: 52310

Using an extension method would be helpful in this scenario.

public static class EventExtension
{
    public static void RaiseEvent<T>(this EventHandler<T> handler, object obj, T args) where T : EventArgs
    {
        if (handler != null)
        {
            handler(obj, args);
        }
    }
}

It can then be used like below.

public event EventHandler<YourEventArgs> YourEvent;
...
YourEvent.RaiseEvent(this, new YourEventArgs());

Upvotes: 1

Robert Paulson
Robert Paulson

Reputation: 18061

The recommended pattern is (.net 2.0+)

public class MyClass
{
    public event EventHandler<EventArgs> MyEvent; // the event

    // protected to allow subclasses to override what happens when event raised.
    protected virtual void OnMyEvent(object sender, EventArgs e)
    {
        // prevent race condition by copying reference locally
        EventHandler<EventArgs> localHandler = MyEvent;
        if (localHandler != null)
        {
            localHandler(sender, e);
        }
    }
    public void SomethingThatGeneratesEvent()
    {
        OnMyEvent(this, EventArgs.Empty);
    }
}

I see a lot of recommendations for an empty delegate{} in an initializer, but I totally disagree with it. If you follow the above pattern you only check the event != null in one place. The empty delegate{} initializer is a waste because it's an extra call per event, it wastes memory, and it still can fail if MyEvent was set to null elsewhere in my class.

* If your class is sealed, you wouldn't make OnMyEvent() virtual.

Upvotes: 4

xyz
xyz

Reputation: 27837

Thank you for the responses. I do understand why the NullReferenceException happens and how to get around it.

Gishu said

What is the point of raising an event if no one is listening?

Well, maybe it's a terminology thing. The appeal of an "event" system seems to me that all the responsibility of the fallout of the event that took place should be on the watchers and not the performer.


Perhaps a better thing to ask is: If a delegate field is declared with the event keyword in front of it, why doesn't the compiler translate all instances of:

MadeSound(this, EventArgs.Empty)

to

if (MadeSound != null) { MadeSound(this, EventArgs.Empty); }

behind the scenes in the same manner that other syntax shortcuts are? The number of boilerplate OnSomeEvent null checking methods that people have to write manually must be colossal.

Upvotes: 0

user27414
user27414

Reputation:

Very Zen, eh?

You have to test for null when you want to raise an event:

protected void OnMyEvent()
{
    if (this.MyEvent != null) this.MyEvent(this, EventArgs.Empty);
}

It would be nice if you didn't have to bother with this, but them's the breaks.

Upvotes: 3

Gishu
Gishu

Reputation: 136613

What is the point of raising an event if no one is listening? Technically, its just how C# chose to implement it.

In C#, an event is a delegate with some special feathers. A delegate in this case can be viewed as a linked list of function pointers (to handler methods of subscribers). When you 'fire the event' each function pointer is invoked in turn. Initially the delegate is a null object like anything else. When you do a += for the first subscribe action, Delegate.Combine is called which instantiates the list. (Calling null.Invoke() throws the null exception - when the event is fired.)

If you still feel that "it must not be", use a helper class EventsHelper as mentioned here with old and improved 'defensive event publishing' http://weblogs.asp.net/rosherove/articles/DefensiveEventPublishing.aspx

Upvotes: 2

Chris Marasti-Georg
Chris Marasti-Georg

Reputation: 34650

Another good way I've seen to get around this, without having to remember to check for null:

class Tree
{
    public event EventHandler MadeSound = delegate {};

    public void Fall() { MadeSound(this, new EventArgs()); }

    static void Main(string[] args)
    {
        Tree oaky = new Tree();
        oaky.Fall();
    }
}

Note the anonymous delegate - probably a slight performance hit, so you have to figure out which method (check for null, or empty delegate) works best in your situation.

Upvotes: 4

Jon Skeet
Jon Skeet

Reputation: 1500375

You need to understand what your event declaration is actually doing. It's declaring both an event and a variable, When you refer to it within the class, you're just referring to the variable, which will be null when there are no subscribers.

Upvotes: 3

Mitchel Sellers
Mitchel Sellers

Reputation: 63126

James provided a good technical reasoning, I would also like to add that I have seen people use this an advantage, if no subscribers are listening to an event, they will take action to log it in the code or something similar. A simpl example, but fitting in this context.

Upvotes: 2

James Curran
James Curran

Reputation: 103495

Well, the canonical form is:

void OnMadeSound()
{
    if (MadeSound != null)
    {
        MadeSound(this, new EventArgs());
    }
}

public void Fall() {  OnMadeSound(); }

which is very slightly faster that calling an empty delegate, so speed won out over programming convenience.

Upvotes: 6

Related Questions