Objects with dense events, but sparse usage can benefit from custom event storage.

I'm never sure how many people break open ILDasm on a daily basis, so I figure I'll do it for them whenever I find something great to share. The Windows Forms libraries are littered with classes that have hundreds of static properties with names like EventKeyDown... These properties house readonly objects, not special objects, just objects. You might be familiar with using objects as locking variables, but instead the Windows Forms libraries are using them as identification keys.

In turn, there is not a single instance field backing any of the public facing events that each control exhibits. Looking at System.Windows.Forms.Control there are around 60 events total. That is utterly nasty and if you had 50 controls on a form, that would add up to 2500 separate fields holding delegates. So what are they doing to get around this? They use custom event storage. Each of the publicly facing events provides add/remove overrides in order to storage delegates in a specialized event handling list. If you are writing a derived control, you'll be able to access this special list through Events.

private static readonly object EventCustom = new object();
public event EventHandler Custom {
    add { Events.AddHandler(EventCustom, value); }
    remove { Events.RemoveHandler(EventCustom, value); }
}

What is nice about this new method, is that we only allocate storage when the events get used. Of the 60 events that exist, probably only 2 or 3 are ever hooked by an application. That results in a huge savings of 55 or more fields per every Control instance. While we save a great deal of space, we have a small trade-off in access speed. You see internally the EventHandlerList is implemented as a simple linked list. That means we have to search the entire list from front to back in order to find a specific event and whenever we search for non-existent events we always have to navigate the entire collection. Usage scenarios show that both performance and memory consumption are optimized using this method even with the non-existent search penalties. Whenever we need to call our handlers, we can use the indexed property.

EventHandler eh = Events[EventCustom] as EventHandler;
if ( eh != null ) { eh(this, null); }

Well, that looks pretty decent and quite easy to implement as well. We'll get the same performance benefits as the rest of the Windows Forms controls. We can even extend the model to add additional features like enforcing that only instance methods can be used as handlers and only instances of a specific class.

add { if ( value.Target is SecuredType ) { Events.AddHandler(EventCustom, value); } }

You may be curious why they chose to use a linked list over a different type of collection? Well, for the same reason certain teams use the HybridDictionary as opposed to just a Hashtable. In many cases the searching of a list is actually faster than a Hashtable look-up. The cut-off for the HybridDictionary is around 10 elements. With that being the case you'll very rarely hook more than 10 events on a given object making the linked list faster than something like a Hashtable. You may wonder then why they didn't just use a HybridDictionary just in case? Well, that allocates a back-end array that is larger than the number of events hooked as well and therefore wastes a bit of space. It would take a lot of testing, but you'd probably be able to prove the efficiency of the linked list against any other options.

Published Wednesday, September 22, 2004 6:55 AM by Justin Rogers

Comments

Wednesday, September 22, 2004 11:17 AM by Jerry Pisk

# re: Objects with dense events, but sparse usage can benefit from custom event storage.

Is it really worth it? even if you have 2,500 extra fields that would translate to some 10K of memory to store the handler references. I use a lot more than that for temporary buffers, saving 10K of RAM doesn't seem like a big deal.
Wednesday, September 22, 2004 7:11 PM by Justin Rogers

# re: Objects with dense events, but sparse usage can benefit from custom event storage.

Temporary memory buffers are reclaimed. This is permanently allocated memory attached to a long running object. There are some differences there in that the 10k for the temporary buffers will be constantly re-used and reclaimed.

What you get from custom storage is faster object instantiation, smaller object layouts on the heap, etc... If there is a way to examine this in terms of performance, I think most of the bottleneck would be at the processor level, but I'll see if there are any out of the box tests that might make a case.

# Ryan’s Tech Blog » EventHandlerList, key equality, and auto-boxing in C#

Pingback from  Ryan’s Tech Blog » EventHandlerList, key equality, and auto-boxing in C#

Leave a Comment

(required) 
(required) 
(optional)
(required)