One of the more obscure features of C# is the ability to specify custom overloads for adding and removing event registration similarly to properties, via the add and remove keywords. Known as "event accessors," they implement the parts of event registration that the C# compiler normally handles. You didn't think that that += operator was implemented on the type, did you?
1: class Test
2: {
3: public event EventHandler Event1;
4:
5: private EventHandler ev2;
6: public event EventHandler Event2
7: {
8: add
9: {
10: if (ev2 != null)
11: ev2 = (EventHandler)Delegate.Combine(ev2, value);
12: else
13: ev2 = value;
14: }
15: remove
16: {
17: if (ev2 != null)
18: ev2 = (EventHandler)Delegate.Remove(ev2, value);
19: }
20: }
21: protected virtual void OnEvent2(EventArgs e)
22: {
23: if (ev2 != null)
24: ev2(this, e);
25: }
26: }
27:
This pattern is actually used extensively throughout the Windows Forms library, where controls add event handlers to base event handler collections implemented within a hashtable. I can only surmise that this is done to prevent having dozens of event fields cluttering up the classes.
Now, if we were to compile this app and disassemble it in Reflector, we'd get a very similar picture to what we've got. Reflector would show the compiler-generated add/remove blocks for Event1, though not when the event declaration is selected, and it also indicates that there are compiler directives that show the event accessors are synchronized.
Visual Basic .NET also supports this pattern, but adds an additional keyword: the RaiseEvent keyword:
1: Public Class Test
2: Public Event Event1 As EventHandler
3:
4: Private ev2 As EventHandler
5: Public Custom Event Event2 As EventHandler
6: AddHandler(ByVal value As EventHandler)
7: If Not ev2 Is Nothing Then
8: ev2 = CType(System.Delegate.Combine(ev2, value), EventHandler)
9: Else
10: ev2 = value
11: End If
12: End AddHandler
13:
14: RemoveHandler(ByVal value As EventHandler)
15: If Not ev2 Is Nothing Then
16: ev2 = CType(System.Delegate.Remove(ev2, value), EventHandler)
17: End If
18: End RemoveHandler
19:
20: RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
21: ev2(sender, e)
22: End RaiseEvent
23: End Event
24:
25: Protected Overridable Sub OnEvent2(ByVal e As EventArgs)
26: If Not ev2 Is Nothing Then
27: RaiseEvent Event2(Me, e)
28: End If
29: End Sub
30: End Class
In this example, Visual Basic allows you to implement exactly how Event2 is raised. When I look at this in Reflector to see how C# uses this, here's what I see:
Reflector gives C# the raise keyword. Why haven't the C# language experts done so?
How would this be worthwhile? Well, suppose that we're building an application that can have plugins. We don't know that plugins are always going to work correctly, so when they handle an event, they may raise an exception. The problem is, if an event is invoked and the first event handler causes an exception, none of the successive handlers will be invoked.
Arguably, the "state of the application is undefined after an exception is raised, so we should gracefully exit." But that's not always the case! What if the way to gracefully do this is to analyze the stack trace within the application, determine which plugin caused the exception, and unload the plugin? We can't do any of this from C#.
Give us the raise keyword!
This is the end of my "C# 4.0 Wishlist" series. For reference, here are the other articles: