Implementing events more efficiently in .NET applications

One of the things that frequently annoys me about third party controls (including those built into the .NET Framework) are properties that either aren't virtual, or don't have corresponding change events / virtual methods. Quite often I find myself wanting to perform an action when a property is changed, and if neither of those are present I end up having to create a custom version of the property, and as a rule, I don't like using the new keyword unless there is no other alternative.

As a result of this, whenever I add properties to my WinForm controls, I tend to ensure they have a change event, and most often they are also virtual as I have a custom code snippet to build the boilerplate. That can mean some controls have an awful lot of events (for example, the ImageBox control has (at the time of writing) 42 custom events on top of those it inherits, some for actions but the majority for properties). Many of these events will be rarely used.

As an example, here is a typical property and backing event

private bool _allowUnfocusedMouseWheel;

[Category("Behavior"), DefaultValue(false)]
public virtual bool AllowUnfocusedMouseWheel
{
  get { return _allowUnfocusedMouseWheel; }
  set
  {
    if (_allowUnfocusedMouseWheel != value)
    {
      _allowUnfocusedMouseWheel = value;

      this.OnAllowUnfocusedMouseWheelChanged(EventArgs.Empty);
    }
  }
}

[Category("Property Changed")]
public event EventHandler AllowUnfocusedMouseWheelChanged;

protected virtual void OnAllowUnfocusedMouseWheelChanged(EventArgs e)
{
  EventHandler handler;

  handler = this.AllowUnfocusedMouseWheelChanged;

  handler?.Invoke(this, e);
}

Quite straightforward - a backing field, a property definition, a change event, and a protected virtual method to raise the change event the "safe" way. It's an example of an event that will be rarely used, but you never know and so I continue to follow this pattern.

Despite all the years I've been writing C# code, I never actually thought about how the C# compiler implements events, beyond the fact that I knew it created add and remove methods, in a similar fashion to how a property creates get and set methods.

From browsing the .NET Reference Source in the past, I knew the Control class implemented events slightly differently to above, but I never thought about why. I assumed it was something they had done in .NET 1.0 and never changed with Microsoft's mania for backwards compatibility.

I am currently just under halfway through CLR via C# by Jeffrey Richter. It's a nicely written book, and probably would have been of great help many years ago when I first started using C# (and no doubt as I get through the last third of the book I'm going to find some new goodies). As it is, I've been ploughing through it when I hit the chapter on Events. This chapter started off by describing how events are implemented by the CLR and expanding on what I already knew. It then dropped the slight bombshell that this is quite inefficient as it requires more memory, especially for events that are never used. Given I liberally sprinkle my WinForms controls with events and I have lots of other classes with events, mainly custom observable collections and classes implementing INotifyPropertyChanged (many of those!), it's a safe bet that I'm using a goodly chunk of ram for no good reason. And if I can save some memory "for free" as it were... well, every little helps.

The book then continued with a description of how to explicitly implement an event, which is how the base Control class I mentioned earlier does it, and why the reference source code looked different to typical. While the functionality is therefore clearly built into .NET, he also proposes and demonstrates code for a custom approach which is possibly better than the built in version.

In this article, I'm only going to cover what is built into the .NET Framework. Firstly, because I don't believe in taking someone else's written content, deleting the introductions and copyright information and them passing it off as my own work. And secondly, as I'm going to start using this approach with my myriad libraries of WinForm controls, their base implementations already have this built in, so I just need to bolt my bits on top of it.

How big is my class?

Before I made any changes to my code, I decided I wanted to know how much memory the ImageBox control required. (Not that I doubted Jeffrey, but it doesn't hurt to be cautious, especially given the mountain of work this will entail if I start converting all my existing code). There isn't really a simple way of getting the size of an object, but this post on StackOverflow (where else!) has one method.

unsafe
{
  RuntimeTypeHandle th = typeof(ImageBox).TypeHandle;
  int size = *(*(int**)&th + 1);

  Console.WriteLine(size);
}

When running this code in the current version of the ImageBox, I get a value of 968. It's a fairly meaningless number, but does give me something to compare. However, as I didn't quite trust it I also profiled the demo program with a memory profiler. After profiling, dotMemory also showed the size of the ImageBox control to be 968 bytes. Lucky me.

Explicitly implementing an event

At the start of the article, I showed a typical compiler generated event. Now I'm going to explicitly implement it. This is done by using a proxy class to store the event delegates. So instead of having delegates automatically created for each event, they will only be created when explicitly binding the event. This is where Jeffrey prefers a custom approach, but I'm going to stick with the class provided by the .NET Framework, the EventHandlerList class.

As the proxy class is essentially a dictionary, we need a key to identify the event. As we're trying to save memory, we create a static object which will be used for all occurrences of this event, no matter how many instances of our component are created.

private static readonly object EventAllowUnfocusedMouseWheelChanged = new object();

Next, we need to implement the add and remove accessors of the event ourselves

public event EventHandler AllowUnfocusedMouseWheelChanged
{
  add
  {
    this.Events.AddHandler(EventAllowUnfocusedMouseWheelChanged, value);
  }
  remove
  {
    this.Events.RemoveHandler(EventAllowUnfocusedMouseWheelChanged, value);
  }
}

As you can see, the definition is the same, but now we have created add and remove accessors which call either the AddHandler or RemoveHandler methods of a per-instance EventHandlerList component, using the key we defined earlier, and of course the delegate value to add or remove.

In a WinForm's control, this is automatically provided via the protected Events property. If you're explicitly implementing events in a class which doesn't offer this functionality, you'll need to create and manage an instance of the EventHandlerList class yourself

Finally, when it's time to invoke the method, we need to retrieve the delegate from the EventHandlerList, once again with our event key, and if it isn't null, invoke it as normal.

protected virtual void OnAllowUnfocusedMouseWheelChanged(EventArgs e)
{
  EventHandler handler;

  handler = (EventHandler)this.Events[EventAllowUnfocusedMouseWheelChanged];

  handler?.Invoke(this, e);
}

There are no generic overloads, so you'll need to cast the returned Delegate into the appropriate EventHandler, EventHandler<T> or custom delegate.

Simple enough, and you can easily have a code snippet do all the grunt work. The pain will come from if you decide to convert existing code.

Does this break anything?

No. You're only changing the implementation, not how other components interact with your events. You won't need to make any code changes to any code that interacts with your updated component, and possibly won't even need to recompile the other code (strong naming and binding issues aside!).

In other words, unless you do something daft like change your the visibility of your event, or accidentally rename it, explicitly implementing a previously implicitly defined event is not a breaking change.

How big is my class, redux

I modified the ImageBox control (you can see the changed version on this branch in GitHub) so that all the events were explicitly implemented. After running the new version of the code through the memory profiler / magic unsafe code, the size of the ImageBox is now 632 bytes, knocking nearly a third of the size off. No magic bullet, and isn't a full picture, but I'll take it!

In all honesty, I don't know if this has really saved memory or not. But I do know I have a plethora of controls with varying numbers of events. And I know Jeffrey's CLR book is widely touted as a rather good tome. And I know this is how Microsoft have implemented events in the base Control classes (possibly elsewhere too, I haven't looked). So with all these "I knows", I also know I'm going to have all new events follow this pattern in future, and I'll be retrofitting existing code when I can.

An all-you-can-eat code snippet

I love code snippets and tend to create them whenever I have boilerplate code to implement repeatedly. In fact, most of my snippets actually are variations of property and event implementations, to handle things like properties with change events, or properties in classes that implement INotifyPropertyChanged and other similar scenarios. I have now retired my venerable basic property-with-event and standalone-event snippets with new versions that do explicit event implementing. As I haven't prepared a demonstration program for this article, I instead present this code snippet for generating properties with backing events - I hope someone finds them as useful as I do.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Property with Backing Event</Title>
      <Shortcut>prope</Shortcut>
      <Description>Code snippet for property with backing field and a change event</Description>
      <Author>Richard Moss</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>Property type</ToolTip>
          <Default>int</Default>
        </Literal>
        <Literal>
          <ID>name</ID>
          <ToolTip>Property name</ToolTip>
          <Default>MyProperty</Default>
        </Literal>
        <Literal>
          <ID>field</ID>
          <ToolTip>The variable backing this property</ToolTip>
          <Default>myVar</Default>
        </Literal>
      </Declarations>
      <Code Language="csharp"><![CDATA[private $type$ $field$;

    [Category("")]
    [DefaultValue("")]
    public $type$ $name$
    {
      get { return $field$; }
      set
      {
        if ($field$ != value)
        {
          $field$ = value;

          this.On$name$Changed(EventArgs.Empty);
        }
      }
    }

    private static readonly object Event$name$Changed = new object();

    /// <summary>
    /// Occurs when the $name$ property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler $name$Changed
    {
      add
      {
        this.Events.AddHandler(Event$name$Changed, value);
      }
      remove
      {
        this.Events.RemoveHandler(Event$name$Changed, value);
      }
    }

    /// <summary>
    /// Raises the <see cref="$name$Changed" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void On$name$Changed(EventArgs e)
    {
      EventHandler handler;
    
      handler = (EventHandler)this.Events[Event$name$Changed];
    
      handler?.Invoke(this, e);
    }

  $end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

About The Author

Gravatar

The founder of Cyotek, Richard enjoys creating new blog content for the site. Much more though, he likes to develop programs, and can often found writing reams of code. A long term gamer, he has aspirations in one day creating an epic video game. Until that time, he is mostly content with adding new bugs to WebCopy and the other Cyotek products.

Leave a Comment

While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?

Styling with Markdown is supported