Kyle Baran
Kyle Baran

Reputation: 1839

How do I work with explicit interface events?

So I made a few interfaces like this:

public interface IDrawActions : ISimpleDrawable
{
    Action<GameTime> PreDrawAction { get; set; }
    Action<GameTime> PostDrawAction { get; set; }

    event EventHandler PreDrawActionChanged;
    event EventHandler PostDrawActionChanged;
}

Any classes that implement this (or several) of these interfaces got kinda cluttered, so I thought it would make sense to use explicit interface implementation to hide the uncommon events and properties away. But, doing so, I got a compiler error:

An explicit interface implementation of an event must use event accessor syntax

And Googling it lead me to this rather helpful blog post:

This hints at one of the primary reasons for writing your own add and remove accessors: to provide your own underlying data store. One reason you might want to do this is if you have lots of exposed events on your class, but in such a way that only a few are typically in use on an instance at any point in time. In such a scenario, there can be significant memory overhead associated with maintaining a delegate field for each event.

How exactly does this save on resources? It seems that the delegate invocation list for the event would be null, but how and when would it actually be instantiated if you're using your own custom handlers? Everything is hidden away!

Upvotes: 4

Views: 1370

Answers (1)

Frank Hileman
Frank Hileman

Reputation: 1239

The text in bold refers to a memory optimization which is extremely useful when you have many events, or many object instances, all with many events. The most basic support for creating events in C# is to use the event keyword. This keyword is syntactic sugar for the following generated code:

  • A field to contain a delegate. Delegates form linked lists. This is the head of the list, and additions are inserted at the head.
  • Event accessors, where the add method inserts into the linked list using the delegate field, and the remove method removes from the linked list. The linked list addition and removal also has syntactic sugar hiding it, so you see only "+=" and "-=" to add to, or remove from, the delegate list.

In this sense, the event keyword produces code similar to the generated code from C# auto-implemented properties.

The overhead comes in maintaining a separate field for each event. This is not necessary, just as it is not necessary to maintain a separate field for the data backing each property exposed by a class. We can virtualize both event fields and property fields.

How do we eliminate the overhead for events specifically? We use this method in libraries such as VG.net, and Microsoft uses similar methods in their code: keep a collection of events in a single field. In most cases, few instances have many event subscribers. The simplest collection is a linked list of class instances. Each element in the collection consists of a class instance containing the following properties:

  • An event identifier. There is one unique identifier per unique type of event. It is best to use something small, like a byte or integer, since you are unlikely to have millions of event types, even across a huge library.
  • A delegate. The delegate can be weakly typed (Delegate).

When you need to add an event handler for a subscriber, you look up the delegate in your collection, using the unique event type identifier. The first time you look it up, the collection will not contain it. In the case of event handler additions, you will add an element to your collection, and within that element, add to the delegate stored there, using Delegate.Combine. To remove a handler, you use Delegate.Remove.

Here is an example from real code in VG.net:

    private static readonly int MouseDownEvent = EventsProperty.CreateEventKey();

    public event ElementMouseEventHandler MouseDown
    {
        add { AddHandler(MouseDownEvent, value); }
        remove { RemoveHandler(MouseDownEvent, value); }
    }

    public virtual void OnMouseDown(ElementMouseEventArgs args)
    {
        ElementMouseEventHandler handler = 
            FindHandler(MouseDownEvent) as ElementMouseEventHandler;
        if (handler != null)
            handler(this, args);
    }

    internal void AddHandler(int key, Delegate value)
    {
        EventsProperty p = (EventsProperty)GetOrInsertProperty(EventsProperty.Key);
        p.AddHandler(key, value);
    }

    internal void RemoveHandler(int key, Delegate value)
    {
        EventsProperty p = (EventsProperty)GetProperty(EventsProperty.Key);
        if (p == null)
            return;
        p.RemoveHandler(key, value);
    }

    internal Delegate FindHandler(int key)
    {
        EventsProperty p = (EventsProperty)GetProperty(EventsProperty.Key);
        if (p == null)
            return null;
        return p[key];
    }

We virtualized not only events, but properties as well. For VG.net, all events are contained in one virtual property (EventProperty), and most public properties are also virtualized, although we bundle together property values that are most likely used together. This enables us to provide many properties and events on all instances, while there is zero memory used by these properties or events per instance, unless:

  • For properties, the property is set to a non-default value.
  • For events, something subscribes to the event.

These types of optimization make VG.net efficient even when there are millions of graphical objects in memory, even if running on low-end hardware.

Ideally, we should have programming tools that do not force us to optimize data structures explicitly. Specifying exactly how objects are laid out in memory is a burden better handled by a profiler or smart run-time system. We are still in the stone age in this respect, in every programming language I have ever worked in.

Upvotes: 1

Related Questions