3

I'm trying to properly understand Events and EventArgs but can't quite get a handle on the whole EventArgs.Empty property.

EventArgs implements:

public static readonly EventArgs Empty;

and allows us to create an EventHandler and call it using:

public event EventHandler<EventArgs> TestHappening;

private void MyMethod()
{
    TestHappening( this, EventArgs.Empty );
}

Now I've studied a number of classes based on EventArgs and none of them seem to implement this so I'm flying a little blind even though I've read all the documentation I could find regarding EventArgs.Empty. According to the documentation, "The value of Empty is a read-only instance of EventArgs equivalent to the result of calling the EventArgs constructor".

Based on that I've created the following implementation:

public class TestEventArgs : EventArgs
{
    public static readonly TestEventArgs Empty;

    public bool UpdatedValue { get; private set; }

    TestEventArgs()
        : this( false )
    {
    }

    public TestEventArgs( bool updatedValue )
    {
        this.UpdatedValue = updatedValue;
    }
}

public event EventHandler<TestEventArgs> TestHappening;

private void MyMethod()
{
    TestHappening( this, EventArgs.Empty );
}

Is the use of TestEventArgs.Empty instancing a class or what exactly is it doing?

Also, even though all the subclasses I checked didn't make use of Empty, they still have it available, isn't it confusing having an unusable property exposed?

Lastly, based on the various documents I studied, there were two main variances of when to actually instantiate the EventArgs. Which is considered "more" correct?:

OnTestHappening( new TestEventArgs( false ) );

private void OnTestHappening( TestEventArgs e )
{
    var handler = TestHappening;

    if ( handler != null )
        handler( this, e );
}

vs

OnTestHappening( true );

private void OnTestHappening( bool foo )
{
    var handler = TestHappening;

    if ( handler != null )
        handler( this, new TestEventArgs( foo ) );
}
Storm
  • 1,848
  • 4
  • 20
  • 39
  • 1
    The EventArgs class is designed to be a base class. For more specialized event classes that actually have useful properties. You don't know when you *may* need such a class some day. So you start with EventArgs and can very easily change it without breaking any event handlers. Until that day comes, you need to pass an instance of EventArgs. You can use `new EventArgs()` but that's wasteful, EventArgs.Empty is a cheap alternative. – Hans Passant Jun 02 '15 at 13:51
  • 1
    It doesn't even make sense to have an "empty" instance of this class in the first place. When you created this class you should expect people to actually provide a boolean when creating an instance, rather than using a default value. – Servy Jun 02 '15 at 13:51
  • 1
    @Servy : I agree, I'm just trying to fully understand how it works and figured if I broke it down like I did, it might help some other lost soul some day :) – Storm Jun 02 '15 at 14:10
  • 1
    @Storm Understanding how to implement something that *doesn't make sense in the context you're trying to implement it in* isn't teaching you how to use that technique properly. If you want to learn how to use a technique, you should be using it in a context where it's the appropriate tool for the job. – Servy Jun 02 '15 at 14:11

1 Answers1

2

You should think for yourself if you really need the Empty field. If you don't instead to use it, you shouldn't create it. The EventArgs class doesn't have any variables or properties, so it doesn't make sense to create a new instance every time. In your case, since you have a default value, it does make more sense to have two 'empty' TestEventArgs, one for true and one for false (if you really want to and it makes sense in your scenario).

You are missing some other points in your implementation which I have fixed below:

public class TestEventArgs : EventArgs
{
    public static readonly TestEventArgs True = new TestEventArgs(true);

    public static readonly TestEventArgs False = new TestEventArgs(false);

    public bool UpdatedValue { get; private set; }

    public TestEventArgs(bool updatedValue)
    {
        this.UpdatedValue = updatedValue;
    }

    public event EventHandler<TestEventArgs> TestHappening;

    private void MyMethod()
    {
        EventHandler<TestEventArgs> eh = TestHappening;

        eh?.Invoke(this, TestEventArgs.True);
    }
}

What I have changed:

  1. Instantiated Empty as a new TestEventArgs, since that was the definition of EventArgs.Empty too.
  2. I have implemented the thread-safe version of the event handler (the second sample in your code). If the list of event handlers subscribed to the event changes, your first sample isn't safe. Your second is, and therefore you should that option.

Regarding your last point: it depends if you intend to have the calling delegates change the object (it is passing one instance vs multiple instances). In your case, since the instance can't be altered, it doesn't matter that much.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Ok, that makes a lot of sense now, I just couldn't make heads or tails of that, I must have been over thinking it. Regarding the thread safety, thanks for pointing that out, I'm still trying to actually get that one under the knee, why is `TestHappening( this, EventArgs.Empty )` not thread safe and do you have a link to something that explains it further? I've read numerous write-ups on thread safety but just can't seem to get it nailed down – Storm Jun 02 '15 at 14:03
  • Awesome Patrick, could you also just answer my 3rd question above in your answer please? – Storm Jun 02 '15 at 14:07
  • Why do you use the keyword `new` before `readonly`? I think it's redundant because `True` field hides nothing, or am I wrong? `eh(this, TestEventArgs.Empty);` line won't compile because `Empty` returns the base type EventArgs, which cannot be downcasted to TestEventArgs. Your example would make more sense if you were hiding the `Empty` field instead of creating `True` and `False`. Appart from that, you might also want to remove `private set` from `UpdateValue` because it's readonly and maybe update the thread-safe invocation to `TestHappening?.Invoke(this, TestEventArgs.True);` – fibriZo raZiel Oct 10 '19 at 11:11
  • 1
    @fibriZoraZiel You are right. Fixed. Thanks for commenting. – Patrick Hofman Oct 10 '19 at 12:04