7

I've seen some very good questions on Stack Overflow concerning delegates, events, and the .NET implementation of these two features. One question in particular, "How do C# Events work behind the scenes?", produced a great answer that explains some subtle points very well.

The answer to the above question makes this point:

When you declare a field-like event ... the compiler generates the methods and a private field (of the same type as the delegate). Within the class, when you refer to ElementAddedEvent you're referring to the field. Outside the class, you're referring to the field

An MSDN article linked from the same question ("Field-like events") adds:

The notion of raising an event is precisely equivalent to invoking the delegate represented by the event — thus, there are no special language constructs for raising events.

Wanting to examine further, I built a test project in order to view the IL that an event and a delegate are compiled to:

public class TestClass
{
    public EventHandler handler;
    public event EventHandler FooEvent;

    public TestClass()
    { }
}

I expected the delegate field handler and the event FooEvent to compile to roughly the same IL code, with some additional methods to wrap access to the compiler-generated FooEvent field. But the IL generated wasn't quite what I expected:

.class public auto ansi beforefieldinit TestClass
    extends [mscorlib]System.Object
{
    .event [mscorlib]System.EventHandler FooEvent
    {
        .addon instance void TestClass::add_FooEvent(class [mscorlib]System.EventHandler)
        .removeon instance void TestClass::remove_FooEvent(class [mscorlib]System.EventHandler)
    }

    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        // Constructor IL hidden
    }

    .field private class [mscorlib]System.EventHandler FooEvent
    .field public class [mscorlib]System.EventHandler handler
}

Since events are nothing more than delegates with compiler-generated add and remove methods, I didn't expect to see events treated as anything more than that in IL. But the add and remove methods are defined in a section that begins .event, not .method as normal methods are.

My ultimate questions are: if events are implemented simply as delegates with accessor methods, what is the point of having a .event IL section? Couldn't they be implemented in IL without this by using .method sections? Is .event equivalent to .method?

Community
  • 1
  • 1
Chris
  • 747
  • 3
  • 11
  • 23

3 Answers3

7

I'm not sure that is surprising... compare to the same for properties vs fields (since properties before the same function as events: encapsulation via accessors):

.field public string Foo // public field
.property instance string Bar // public property
{
    .get instance string MyType::get_Bar()
    .set instance void MyType::set_Bar(string)
}

Also - events do not mention anything about fields; they only define the accessors (add/remove). The delegate backer is an implementation detail; it just so happens that field-like-events declare a field as a backing member - in the same way that auto-implemented-properties declare a field as a backing member. Other implementations are possible (and very common, especially in forms etc).

Other common implementations:

Sparse-events (Controls, etc) - EventHandlerList (or similar):

// only one instance field no matter how many events;
// very useful if we expect most events to be unsubscribed
private EventHandlerList events = new EventHandlerList();
protected EventHandlerList Events {
    get { return events; } // usually lazy
}

// this code repeated per event
private static readonly object FooEvent = new object();
public event EventHandler Foo
{
    add { Events.AddHandler(FooEvent, value); }
    remove { Events.RemoveHandler(FooEvent, value); }
}
protected virtual void OnFoo()
{
    EventHandler handler = Events[FooEvent] as EventHandler;
    if (handler != null) handler(this, EventArgs.Empty);
}

(the above is pretty-much the backbone of win-forms events)

Facade (although this confuses the "sender" a little; some intermediary code is often helpful):

private Bar wrappedObject; // via ctor
public event EventHandler SomeEvent
{
    add { wrappedObject.SomeOtherEvent += value; }
    remove { wrappedObject.SomeOtherEvent -= value; }
}

(the above can also be used to effectively rename an event)

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Can you give some more detail about other implementations that are common? I assume you're talking about other implementations than using a field as a backing memeber, not other implementations than using a delegate of any type as a backing memeber. – Chris Oct 20 '08 at 20:54
  • Thanks for the update. How can I specify that I want an event implemented using an EventHandlerList instead of a delegate field as a backing member? If the default behavior of the `event` keyword is to use a delegate field, do I have to bypass C# entirely and write the IL directly? – Chris Oct 21 '08 at 08:33
  • 1
    I wrote it above, already. The default *if you don't specify add/remove* is to use a delegate field. If you specify add/remove (like above) you can do what you like. It would probably be possible to also do this via postsharp, but I'm not sure I'd bother... – Marc Gravell Oct 21 '08 at 09:41
2

Events aren't the same as delegates. Events encapsulate adding/removing a handler for an event. The handler is represented with a delegate.

You could just write AddClickHandler/RemoveClickHandler etc for every event - but it would be relatively painful, and wouldn't make it easy for tools like VS to separate out events from anything else.

This is just like properties really - you could write GetSize/SetSize etc (as you do in Java) but by separating out properties, there are syntactical shortcuts available and better tool support.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • You can do the same thing with delegates, they support += combining (even with null delegates) – Pop Catalin Oct 20 '08 at 20:45
  • 1
    The downside of course is that you can't refactor or build higher level code with events. Like the "Async Event-based" stuff MS is so hot on now -- pain to code to it generically, since you can't reference the event delegate and get its type passed along... – MichaelGG Oct 20 '08 at 21:17
1

The point of having events that are a pair of add, remove, methods is encapsulation.

Most of the time events are used as is, but other times you don't want to store the delegates attached to the event in a field, or you want to do extra processing on add or remove event methods.

For example one way to implement memory efficient events is to store the delegates in a dictionary rather than a private field, because fields are always allocated while a dictionary only grows in size when items are added. This model is similar with what Winforms and WPF uses make make efficient use of memory (winforms and WPF uses keyed dictionaries to store delegates not lists)

Pop Catalin
  • 61,751
  • 23
  • 87
  • 115
  • IMHO, events would be even better encapsulated if there wasn't a remove method--just an "add" method which returned a MethodInvoker which, when invoked, would remove the event. That would allow the Remove handler to receive arbitrary information from the Add handler. For example, if subscriptions were managed via linked list, finding a particular subscription in a remove method given just the delegate would require a linear search, but if removal were done via MethodInvoker, the delegate could contain a reference to the appropriate list node. – supercat Dec 23 '10 at 13:01
  • @supercat, it would have been a nicer method, but a more expensive one. A delegate is quite a memory consumer. Your method would have at least doubled the memory consumtion for the event model due to having 2 delegates for each aded event. Memory consumption due to event bloat is a real issue in .Net applications as it is, it doesn't need to become an even bigger issue. – Pop Catalin Dec 24 '10 at 07:20
  • @Pop: If delegates would use too much memory, one could return an object that implements IUnsubscribe, whose one method would unsubscribe the event. The choice of whether to use a linked list or some other means of holding events is a tradeoff between invocation speed, subscription speed, and unsubscription speed. One advantage of having an object responsible for performing event unsubscription is that the finalizer of such an object can be used to unsubscribe an event. This won't work if the event delegate points to the object where the finalizer is kept, but... – supercat Dec 24 '10 at 15:20
  • @Pop: ...If one wraps the object that receives the event in an object which holds the unsubscriber, and all user references hold the outer object, the subscription will remain alive until, but only until, all references to the wrapped object go away. In any case, having the act of subscribing return an object which would then be used for unsubscribing would allow a wider range of techniques to be used for handling event subscriptions, which could in turn achieve more favorable trade-offs among the factors above. – supercat Dec 24 '10 at 15:24
  • @supercat, such an object (IUnsubscribe) would consume a similar amount of memory that a delegate would (32/64 bits for the reference to the object stored at caller side, + 64/128 bits the object header + 32/64 bits the reference to the event object, + some more memory to indicate the delegate that needs to be removed. – Pop Catalin Dec 24 '10 at 20:04
  • @supercat, also in your scenario loosing such an object would make it imposible to unsubscribe from an event, currently it's not the case, you can unsubscribe with a new delegate that is equivalent with the first. – Pop Catalin Dec 24 '10 at 20:05
  • @Pop Catalin: Perhaps it would be better to return an IDisposable which, when "disposed", would remove the event handler. That would save a little memory versus a delegate, and would fit in well with my pattern of having an object keep a list of what needs to happen to dispose it (to my mind, it's nicer to register an IDisposable field for cleanup at the time it's initialized, than to have a separate part of the code for cleaning up all such fields). A slightly strange-feeling usage of "IDisposable", though. – supercat Jan 01 '11 at 15:42
  • @Pop: As for losing an event-removal object, (1) a properly-written program shouldn't lose the unsubscribed objects; losing an unsubscribed object would constitute a severe bug; (2) provided that the contract for the event-remover was such that it would be safely invokable asynchronously at any time without locking, such an object could have a finalizer to ensure proper event cleanup. This would only help if the event receiver were wrapped in an object which held both the event receiver and unsubscribe method, but such events could be cleanly GC'ed. – supercat Jan 01 '11 at 15:47
  • @Pop Catlin: BTW, if Microsoft thought enough events would be attached that memory for delegates would be any sort of issue, I would think they'd have non-multicast delegate type which simply contained a method pointer and an object reference. – supercat Jan 01 '11 at 18:20
  • @Pop Catlin: Although the most common number of method calls for a delegate is almost certainly one, a MultiCastDelegate includes a lot of other information. An IDisposable or IUnsubscribe would require 4/8 bytes for the reference to the IDisposable/IUnsubscribe object, that object would require an object header and 4/8 bytes for reference to the delegate to be unsubscribed. By contrast, I think a MultiCastDelegate includes some arrays, so using an IDisposable/IUnsubscribe would save a fair bit of storage compared with an unsubscription delegate. – supercat Jan 01 '11 at 18:24
  • @Pop: Sorry for misspelling your name above. – supercat Jan 01 '11 at 18:25