You interact with events in three primary ways: subscribing, unsubscribing and raising them. Like methods and properties, you may find yourself needing to intercept these three interactions. How do you execute code every time that an event is subscribed to? Or raised? Or unsubscribed? PostSharp provides you with a simple mechanism to accomplish this easily.
Intercepting Add and Remove
Throughout the life of an event, it is possible to have many different event handlers subscribe and unsubscribe. You may want to log each of these actions.
Add a reference to the PostSharp package to your project.
Create an aspect that inherits from EventInterceptionAspect. Add the [PSerializableAttribute] custom attribute.
Override the OnAddHandler(EventInterceptionArgs) method and add your logging code to the method body.
Add the
base.OnAddHandler
call to the body of the OnAddHandler(EventInterceptionArgs) method. If this is omitted, the original call to add a handler will not be executed. Unless you want to stop the addition of the handler, you will need to add this line of code.[PSerializable] public class CustomEventHandling : EventInterceptionAspect { public override void OnAddHandler(EventInterceptionArgs args) { base.OnAddHandler(args); Console.WriteLine("A handler was added"); } }
To log the removal of an event handler, override the OnRemoveHandler(EventInterceptionArgs) method.
Add the logging you require to the method body.
Add the
base.OnRemoveHandler
call to the body of the OnRemoveHandler(EventInterceptionArgs) method. Like you saw when overriding the OnAddHandler(EventInterceptionArgs) method, if you omit this call, the original call to remove the handler will not occur.public override void OnRemoveHandler(EventInterceptionArgs args) { base.OnRemoveHandler(args); Console.WriteLine("A handler was removed"); }
Once you have defined the interception points in the aspect, you will need to attach the aspect to the target code. The simplest way to do this is to add the attribute to the event handler definition.
public class Example
{
[CustomEventHandling]
public event EventHandler<EventArgs> SomeEvent;
public void DoSomething()
{
if (SomeEvent != null)
{
SomeEvent.Invoke(this, EventArgs.Empty);
}
}
}
Intercepting Raise
When you are intercepting events, you may also have situations where you want to execute additional code when the event is raised. Raising of an event can occur in many places and you will want to centralize this code to avoid repetition.
Override the OnInvokeHandler(EventInterceptionArgs) method in your aspect class and add the logging you require to the method body.
Add a call to
base.OnInvokeHandler
to ensure that the original invocation occurs.public override void OnInvokeHandler(EventInterceptionArgs args) { base.OnInvokeHandler(args); Console.WriteLine("A handler was invoked"); }
By adding the attribute to the target event handler earlier in this process you have enabled intercepting of each raised event.
Accessing the current context
At any time, the Handler property is set to the delegate being added, removed, or invoked. You can read and write this property. If you write it, the delegate you assign must be compatible with the type of the event. The Event property gets you the EventInfo of the event being accessed.
Within OnInvokeHandler(EventInterceptionArgs), the property Arguments gives access to the arguments with which the delegate was invoked.
These concepts will be illustrated in the following example.
Example: Removing offending event subscribers
When events are subscribed to, the component that raises the event has no way to ensure that the subscriber will behave properly when that event is raised. It's possible that the subscribing code will throw an exception when the event is raised and when that happens you may want to unsubscribe the handler to ensure that it doesn't continue to throw the exception. The EventInterceptionAspect can help you to accomplish this easily.
Override the OnInvokeHandler(EventInterceptionArgs) method in your aspect.
In the method body add a
try...catch
block.In the
try
block add a call tobase.OnInvokeHandler
and in thecatch
block add a call to RemoveHandler(Delegate)[PSerializable] public class CustomEventHandling : EventInterceptionAspect { public override void OnInvokeHandler(EventInterceptionArgs args) { try { base.OnInvokeHandler(args); } catch (Exception e) { Console.WriteLine("Handler '{0}' invoked with arguments {1} failed with exception {2}.", args.Handler.Method, string.Join(", ", args.Arguments.Select(a => a == null ? "null" : a.ToString())), e.GetType().Name); args.RemoveHandler(args.Handler); throw; } } }
Now, any time an exception is thrown during event execution, the offending event handler will be unsubscribed from the event.
Intercepting the event initializer
The OnAddHandler(EventInterceptionArgs) method does not intercept the initializer of a field-like event. If you want to intercept the adding of all handlers, do not use event initializers and instead add the initial handler in the constructor.
public class TargetClass
{
[EventInterception]
public event EventHandler FieldLikeEvent = EventHandler1; // will not be intercepted
public TargetClass()
{
this.FieldLikeEvent += EventHandler2; // will be intercepted
}
}
See Also
Reference
EventInterceptionAspect
Handler
Event
PSerializableAttribute
OnAddHandler(EventInterceptionArgs)
OnAddHandler(EventInterceptionArgs)
OnRemoveHandler(EventInterceptionArgs)
OnInvokeHandler(EventInterceptionArgs)
EventInfo
RemoveHandler(Delegate)