Intercepting Events
Events in .NET are similar to automatic properties. They look like fields, but you can override their underlying methods, Get and Set. Events don’t have Get or Set methods, instead they have Add and Remove methods. PostSharp allows us to intercept these Add and Remove methods as well as the invocation of the event using the EventInterceptionAspect base class. Let’s have a look
[Serializable] public class EventAspect : EventInterceptionAspect { public override void OnAddHandler(EventInterceptionArgs args) { args.ProceedAddHandler(); Console.WriteLine("Handler added"); } public override void OnRemoveHandler(EventInterceptionArgs args) { args.ProceedRemoveHandler(); Console.WriteLine("Handler removed"); } public override void OnInvokeHandler(EventInterceptionArgs args) { args.ProceedInvokeHandler(); Console.WriteLine("Handler invoked"); } }
The aspect is very similar to the other aspects we’ve seen. We simply override the provided virtual methods for the actions we want to intercept - add, remove and invoke. Our test code is as follows
public class Program { static void Main(string[] args) { ExampleClass c = new ExampleClass(); c.SomeEvent += new EventHandler(c_SomeEvent); c.DoSomething(); c.SomeEvent -= c_SomeEvent; Console.ReadKey(); } static void c_SomeEvent(object sender, EventArgs e) { Console.WriteLine("Hello Event!"); } } public class ExampleClass { [EventAspect] public event EventHandler SomeEvent; public void DoSomething() { if (SomeEvent != null) { SomeEvent.Invoke(this, EventArgs.Empty); } } }
The output is as expected
Handler added Hello Event! Handler invoked Handler removed
EventInterceptionAspect
This aspect is pretty straight forward. We’re given three points in which we can intercept Add, Remove and Invoke. As with the other interception aspects, if one or more is applied to a target, the next node in the chain will be invoked and may not be the target event.
EventInterceptionAspect.OnAddHandler
Instead of the Add semantic of the event, the OnAddHandler is invoked instead. This occurs when a new handler is attached to the event (C# +=).
EventInterceptionAspect.OnRemoveHandler
Instead of the Remove semantic of the event, the OnRemoveHandler is invoked instead. This occurs when a delegate is removed from the event (C# -=).
EventInterceptionAspect.OnInvokeHandler
When the event is fired, the OnInvokeHandler method is invoked for each delegate attached to the event. If several handlers have been registered to the event, this method is called once for every handler.
EventInterceptionArgs
Each of the methods that we implement will have an EventInterceptionArgs parameter that we can use to get information and take action.
EventInterceptionArgs.Handler
Handler is the delegate that is currently being added, removed or invoked
EventInterceptionArgs.Instance
Instance is a reference to the instance from which the invocation is occurring, usually the class that the event is a member of. The instance where the invocation will occur can be changed by setting Instance to another class instance.
EventInterceptionArgs.Arguments
Arguments provides access to the arguments passed in during invocation of the event. For example, args.Argument[0] would typically be the value of the “sender” parameter and args.Arguments[1] would be the EventArgs (or some derivation).
EventInterceptionArgs.Event
Event is an instance of System.Reflection.EventInfo containing the reflected information for the target event. For more information on EventInfo, see the MSDN reference.
EventInterceptionArgs.AddHandler
AddHandler is the representation of the Add semantic of the event. You can call this directly to add a handler. It is possible to add additional/specific delegates to the event using AddHandler.
EventInterceptionArgs.ProceedAddHandler
Continues with the original request of adding a delegate to the target event.
EventInterceptionArgs.RemoveHandler
RemoveHandler is the representation of the Remove semantic of the event. You can call this directly to remove a handler. It is possible to remove additional/specific delegates to the event using RemoveHandler.
EventInterceptionArgs.ProceedRemoveHandler
Continues with the original request of removing a delegate from the target event.
EventInterceptionArgs.InvokeHandler
InvokeHandler allows us to invoke the handler, but it allows us to do so by providing a different delegate and arguments. InvokeHandler returns an object which will contain the return value of the delegate (if the delegate is not void).
EventInterceptionArgs.ProceedInvokeHandler
Continues with the original invocation of the delegate with the specified arguments. After invocation, args.ReturnValue may contain a value if the delegate is not void.
EventInterceptionArgs.ReturnValue
ReturnValue will contain the value returned by the delegate is it is not void. The return value can be changed or manipulated by setting ReturnValue to a new value.
Making Events Asynchronous
One of the uses for event interception is to invoke the registered delegates asynchronously. Let’s check out what that aspect looks like
[Serializable] public sealed class AsyncEventAttribute : EventInterceptionAspect { public override void OnInvokeHandler(EventInterceptionArgs args) { Task.Factory.StartNew(() => Invoke(args)); } private static void Invoke(EventInterceptionArgs args) { try { args.ProceedInvokeHandler(); } catch (Exception e) { args.ProceedRemoveHandler(); } } }
This is a very simple aspect. We are only implementing the OnInvokeHandler which has only one job, creating and starting a task. We use Task.Factory.StartNew() to create and immediately start the task asynchronously. We provide the StartNew method with an action which just makes a call to our Invoke method. Tasks are part of the Task Parallel Library which ships with .NET 4.0. If you are not familiar with the TPL or Tasks, please see the MSDN reference.
The Invoke method contains a try/catch structure. We make a call to args.ProceedInvokeHandler and if an exception occurs, we catch it and then remove that delegate from the event by calling args.ProceedRemoveHandler.
To test our aspect, we have modified our example from above.
public class Program { static void Main(string[] args) { ExampleClass c = new ExampleClass(); c.SomeEvent += new EventHandler(c_SomeEvent); c.SomeEvent += new EventHandler(c_SomeEvent); c.SomeEvent += new EventHandler(c_SomeEvent); c.SomeEvent += new EventHandler(c_SomeEvent2); c.SomeEvent += new EventHandler(c_SomeEvent2); c.SomeEvent += new EventHandler(c_SomeEvent2); c.DoSomething(); Console.ReadKey(); } static void c_SomeEvent(object sender, EventArgs e) { Console.WriteLine("Hello Event! Task: " + Task.CurrentId); } static void c_SomeEvent2(object sender, EventArgs e) { Console.WriteLine("Hello Event! Task: " + +Task.CurrentId); } } public class ExampleClass { [AsyncEventAttribute] public event EventHandler SomeEvent; public void DoSomething() { if (SomeEvent != null) { SomeEvent.Invoke(this, EventArgs.Empty); } } }
We register a few handlers with the event and then we call the DoSomething method that is going to invoke the event. Our output shows that the registered handlers were invoked and that they have been invoked in their own task.
Hello Event! Task: 1 Hello Event! Task: 2 Hello Event! Task: 3 Hello Event! Task: 4 Hello Event! Task: 5 Hello Event! Task: 6
Under the Hood
Just for fun, let’s open up ILSpy and look at our example code, you’ll see that there is a lot of work going on.
If you look around, you’ll notice a few things. Even though we didn’t implement OnAddHandler or OnRemoveHandler, PostSharp has modified the event to use <SomeEvent>_Broker to do the adding and removing of handlers.
<SomeEvent>_Broker is a nested class that PostSharp has added and it derives from EventBroker, an abstract class that is used to realize the interception of the invocation.
Notice that the DoSomething method doesn’t call our aspect’s OnInvokeHandler method, nor does it call to the <SomeEvent>_Broker. It simply does the invocation of the event just as it was coded in Visual Studio. How is it that it can intercept the invocation then? When <SomeEvent>_Broker is instantiated in the <>z__InitializeAspects method it gets a reference to our instance. EventBroker is a black box that uses our instance to wrap around the event. We’ll leave it at that for now.