Open sandboxFocusImprove this doc

Intercepting Events

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 a simple mechanism to accomplish this.

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.

  1. Add a reference to the PostSharp package to your project.

  2. Create an aspect that inherits from EventInterceptionAspect. Add the [PSerializableAttribute] custom attribute.

  3. Override the OnAddHandler(EventInterceptionArgs) method and add your logging code to the method body.

  4. 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, 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");
        }
    }
    
  5. To log the removal of an event handler, override the OnRemoveHandler(EventInterceptionArgs) method.

  6. Add the logging you require to the method body.

  7. 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 declaration.

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.

  1. Override the OnInvokeHandler(EventInterceptionArgs) method in your aspect class and add the logging you require to the method body.

  2. 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 earlier in this process, you have enabled interception of each raised event.

Accessing the current context

At any time, the Handler property is set to the delegate being added, removed, or invoked. This property is read-only. To add, remove, or invoke a different delegate, call the AddHandler(Delegate), RemoveHandler(Delegate), or InvokeHandler(Delegate, Arguments) method. The delegate you pass must be compatible with the type of the event. The Event property gives 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. The subscribing code may throw an exception when the event is raised. When that happens, you may want to unsubscribe the handler to ensure that it does not continue to throw the exception. The EventInterceptionAspect can help you to accomplish this.

  1. Override the OnInvokeHandler(EventInterceptionArgs) method in your aspect.

  2. In the method body, add a try...catch block.

  3. In the try block, add a call to base.OnInvokeHandler, and in the catch 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)
OnRemoveHandler(EventInterceptionArgs)
OnInvokeHandler(EventInterceptionArgs)
EventInfo
RemoveHandler(Delegate)