Tom Goff's .Net Musings

Tidbits of information regarding .Net, C#, and SQL Server.

TOTW: EventArgs.Empty

leave a comment »

Events in .Net should always have two parameters: sender and the event arguments. The first parameter is the object that generated the event. The second parameter is either an instance of EventArgs[^] or a class derived from EventArgs.

By using a class derived from EventArgs, developers can include additional information about the event. The following code, shows a typical event definition:

public class MyObject {
    // ... Other code ...

    /// <summary>
    /// This event is fired when something happens.
    /// </summary>
    [field: NonSerialized]
    public event EventHandler<MyCustomEventArgs> SomeEvent;

    /// <summary>
    /// This method is used to fire the <see cref="E:SomeEvent"/>
    /// event.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The event arguments.</param>
    protected virtual void OnSomeEvent(Object sender,
        MyCustomEventArgs e) {
        if (null != SomeEvent)
            SomeEvent(sender, e);
    }
}

In the event above, I specify that the event will use MyCustomEventArgs (not shown). I can then include additional properties in MyCustomEventArgs as needed. There are times when no additional information can be or needs to be included with the event. In these cases, the event typically takes an instance of EventArgs. An example of this is shown here:

public class MyObject {
    // ... Other code ...

    /// <summary>
    /// This event is fired when something happens.
    /// </summary>
    [field: NonSerialized]
    public event EventHandler<EventArgs> SomeEvent;

    /// <summary>
    /// This method is used to fire the <see cref="E:SomeEvent"/>
    /// event.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The event arguments.</param>
    protected virtual void OnSomeEvent(Object sender,
        EventArgs e) {
        if (null != SomeEvent)
            SomeEvent(sender, e);
    }
}

The following code shows an example of how to fire this event. In addition, it shows an overload of OnSomeEvent that provides the appropriate parameters.

public class MyObject {
    // ... Other code ...

    /// <summary>
    /// This method is used to do something important.
    /// </summary>
    public void SomeMethod() {
        // ... Other code ...

        OnSomeEvent(this, new EventArgs());

        // ... Other code ...
    }

    /// <summary>
    /// This method is used to fire the <see cref="E:SomeEvent"/>
    /// event.
    /// </summary>
    protected virtual void OnSomeEvent() {
        if (null != SomeEvent)
            SomeEvent(this, new EventArgs());
    }
}

The code above works great, but has one problem. Every time SomeEvent is fired, we allocate a new instance of EventArgs. Since EventArgs is immutable and has no properties or fields, we could allocate a single global instance of EventArgs and use that single instance every time we fire our event. Luckily, .Net already defines such an instance in EventArgs.Empty[^].

Using this new global instance of EventArgs, our code would look like this:

public class MyObject {
    // ... Other code ...

    /// <summary>
    /// This method is used to do something important.
    /// </summary>
    public void SomeMethod() {
        // ... Other code ...

        OnSomeEvent(this, EventArgs.Empty);

        // ... Other code ...
    }

    /// <summary>
    /// This method is used to fire the <see cref="E:SomeEvent"/>
    /// event.
    /// </summary>
    protected virtual void OnSomeEvent() {
        if (null != SomeEvent)
            SomeEvent(this, EventArgs.Empty);
    }
}

I put together a small sample project[^] to demonstrate the additional allocation. I also included a test of the Empty field from the String class. This is the code from the sample project:

static void Main(string[] args) {
List strings = new List(10000);
List eventArgs = new List(10000);
Int64 before, after;

Console.Write(“Starting \”\” test…”);
strings.Clear();
before = GC.GetTotalMemory(true);
for (Int32 i = 0; i < 10000; i++) strings.Add(""); after = GC.GetTotalMemory(true); Console.WriteLine("done ({0} bytes)", after - before); Console.Write("Starting String.Empty test..."); strings.Clear(); before = GC.GetTotalMemory(true); for (Int32 i = 0; i < 10000; i++) strings.Add(String.Empty); after = GC.GetTotalMemory(true); Console.WriteLine("done ({0} bytes)", after - before); Console.Write("Starting new EventArgs() test..."); eventArgs.Clear(); before = GC.GetTotalMemory(true); for (Int32 i = 0; i < 10000; i++) eventArgs.Add(new EventArgs()); after = GC.GetTotalMemory(true); Console.WriteLine("done ({0} bytes)", after - before); Console.Write("Starting EventArgs.Empty test..."); eventArgs.Clear(); before = GC.GetTotalMemory(true); for (Int32 i = 0; i < 10000; i++) eventArgs.Add(EventArgs.Empty); after = GC.GetTotalMemory(true); Console.WriteLine("done ({0} bytes)", after - before); Console.ReadKey(); }[/sourcecode]Note: I realize GC.GetTotalMemory isn’t the most accurate way to detect memory allocations.

And this is the output from one test run:

emptyfieldtest

As you can see, the only time the total memory is increased is when we create new instances of EventArgs (as expected). Strings have special handling in .Net, such as string interning[^], but there is no special handling for EventArgs. This means you have to specifically use the EventArgs.Empty field.

You won’t see a huge improvement, if any, by using EventArgs.Empty. This is just a good coding practice.

Advertisements

Written by Tom

October 23, 2007 at 8:02 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: