I'm reading this article by Jon Skeet as part of my quest to get a deep understanding of delegates and events.
In the article he demonstrates an event that isn't backed by a delegate variable and states that...
...there are times when you don't want to
back an event with a simple delegate
variable. For instance, in situations
where there are lots of events but
only a few are likely to be subscribed
to, you could have a map from some key
describing the event to the delegate
currently handling it. This is what
Windows Forms does - it means that you
can have a huge number of events
without wasting a lot of memory with
variables which will usually just have
null values.
I don't fully understand what he is saying. Can someone flesh out the examples? For instance, what does he mean by having a "map from some key describing the event to the delegate currently handling it"? How does Windows Forms do this?
Thanks!
You can use the same type yourself - EventHandlerList
. Suppose you have 100 events - that would normally mean having 100 variables, which would take up space even if no-one ever subscribed to the event. Instead of that, EventHandlerList
is a bit like a Dictionary<object, EventHandler>
- it only creates an entry in its internal data structures when you first subscribe to a particular event.
So you might have something like:
// Actual values don't matter; they're just keys
private const string FirstEventKey = "FirstEvent";
private const string SecondEventKey = "SecondEvent";
private readonly EventHandlerList events = new EventHandlerList();
public event EventHandler FirstEvent
{
add { events.AddHandler(FirstEventKey, value); }
remove { events.RemoveHandler(FirstEventKey, value); }
}
public event EventHandler SecondEvent
{
add { events.AddHandler(SecondEventKey, value); }
remove { events.RemoveHandler(SecondEventKey, value); }
}
public void OnFirstEvent(EventArgs e)
{
EventHandler handler = (EventHandler) events[FirstEventKey];
if (handler != null)
{
handler(this, e);
}
}
// Similarly for OnSecondEvent
I'd like to add that this is something I've seen quite often
// bad code
class MyControl : Control {
public event EventHandler ValueChanged;
private CheckBox checked;
// ...
private void InitializeComponent() {
// ...
checked.CheckedChanged += checked_CheckedChanged;
// ...
}
private void checked_CheckedChanged(object sender, EventArgs e) {
if (ValueChanged != null) {
ValueChanged(sender, e);
}
}
}
I consider this an anti-pattern because this way is quicker, takes less memory and is overall simpler I think:
class MyControl : Control {
public event EventHandler ValueChanged {
add {
checked.CheckChanged += value;
}
remove {
checked.CheckChanged -= value;
}
}
private CheckBox checked;
// ...
}
Another situation is the trivial case, where a base class or interface provides an event for something that will never happen in certain derived types. For example, a read-only observable-collection interface might provide a CollectionChangedEvent. An entity holding an interface variable wouldn't be able to use it to change the collection, but might be interested in knowing if/when something else changes the collection. Such an entity should be able to use an immutable collection just as well as a mutable one; from its perspective, an immutable collection should be just like a mutable collection that nobody bothers to mutate while it's watching.
The most logical implementation of the CollectionChangedEvent would be to have add- and remove-handler methods that do nothing, with no backing delegate field. An outside entity that calls the add-handler method is essentially saying "Call me if this collection changes". When it calls the remove-handler method, it's essentially saying "I no longer need to know if this collection changes". If the collection is never ever going to change, those requests can be honored by simply doing nothing.