Archive

When it comes to building complex aspects to solve a specific problem or implement a pattern, the base classes such as OnMethodBoundaryAspect and LocationInterceptionAspect aren’t always up-to the job. We covered the IAspectProvider yesterday, which allows us to dynamically tell PostSharp which aspects to apply to a target at compile time. Today we’re going to build complex aspects that encapsulate multiple transformations in a single aspect.

Advices and Pointcuts

Before we continue, we should cover some vocabulary. We’ve avoided the use of these terms until now to avoid any confusion.

· Advice – “An advice is anything that adds a behavior or a structural element to an element of code. For instance, introducing a method into a class, intercepting a property setter, or catching exceptions, are advices.”

· Pointcut – “A pointcut is a function returning a set of elements of code to which advices apply. For instance, a function returning the set of properties annotated with the custom attribute DataMember is a pointcut.”

When we override the OnEntry method when building an aspect based on OnMethodBoundaryAspect, we’re providing the advice to implement. By default the pointcut would be all methods in a class, if the class was decorated with our aspect based on the OnMethodBoundaryAspect base class.

PostSharp provides us with a set of attributes for declaring advice and pointcuts in any combination under a single aspect. Let’s have a look at an example to give a better picture

[Serializable]
public class ComplexAspect : TypeLevelAspect
{
    private int MethodCounter = 0; //Shared between all advices

    [OnMethodInvokeAdvice, MulticastPointcut(Targets =
                        MulticastTargets.Method, MemberName = "DoSomethingElse")]
    public void OnInvoke(MethodInterceptionArgs args)
    {
        Console.WriteLine("Before method {0}. MethodCounter = {1}", 
args.Method.Name, this.MethodCounter); args.Proceed(); Console.WriteLine("After method {0}. MethodCounter = {1}",
args.Method.Name, this.MethodCounter); } [OnMethodEntryAdvice, MulticastPointcut(Targets = MulticastTargets.Method)] public void OnEntry(MethodExecutionArgs args) { MethodCounter++; Console.WriteLine("Entering {0}. MethodCounter = {1}",
args.Method.Name, this.MethodCounter); } [OnMethodExitAdvice(Master = "OnEntry")] public void OnExit(MethodExecutionArgs args) { Console.WriteLine("Exiting {0}. MethodCounter = {1}",
args.Method.Name, this.MethodCounter); } [OnLocationSetValueAdvice, MulticastPointcut(Targets = MulticastTargets.Property)] public void OnPropertySet(LocationInterceptionArgs args) { MethodCounter++; Console.WriteLine("Setting property: {0} = {1}. MethodCounter = {2}",
args.LocationName, args.Value, this.MethodCounter); } }

Our aspect derives from TypeLevelAspect, not one of the base classes we’ve been using. We have four methods in our aspect and each method is decorated with an advice attribute along with a pointcut attribute.

The OnEntry method is decorated with the OnMethodEntryAdvice attribute which has the same semantics of overriding the OnEntry method in an OnMethodBoundaryAspect. The MulticastPointcut attribute is used and passed the MulticastTargets.Method flag to let PostSharp know that we want to apply this advice to methods in general.

Because we’re using TypeLevelAspect instead of OnMethodBoundaryAspect, we are able to share state between advices. When using OnMethodBoundaryAspect, an instance of our aspect is created for each target. For example, Method1 would have its own copy of our aspect and Method2 would have its own copy. Using TypeLevelAspect to implement our advices changes that behavior; we have a single instance of our aspect that is used for each target which means that the advices get to share state. We’re going to demonstrate this using the MethodCounter field to increment on each method entry and display its value throughout the other advices.

Notice the OnExit advice isn’t specifying a pointcut, but instead is passing in a value to the Master parameter on the OnMethodExitAdvice attribute. We’re defining the master advice method, which means we’re grouping the advices on the same “layer”. OnExit will be a slave method and will inherit the pointcut selectors from OnEntry since only master advice methods can define pointcuts. We only do this for advices of the same category of transformations. For example, you wouldn’t define OnEntry as the master advice method for OnPropertySet because they perform different transformations. We’ll cover grouping on another day.

The other methods in our aspect are the same, just using different advices. The OnInvoke method however has a different pointcut setup. We add an additional parameter, MemberName, giving it a value of “DoSomethingElse” which tells PostSharp to only apply the advice to methods matching “DoSomethingElse”.

Let’s run our example code and look at the result

class Program
{
    static void Main(string[] args)
    {
        ExampleClass ec = new ExampleClass();
        ec.DoSomething();
        ec.DoSomethingElse();
        ec.FirstName = "John";
        ec.LastName = "Smith";

        Console.ReadKey();
    }
}

[ComplexAspect]
public class ExampleClass
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public void DoSomething()
    {
        Console.WriteLine("Doing something");            
    }

    public void DoSomethingElse()
    {
        Console.WriteLine("Doing something else");
    }

}

 
Entering DoSomething. MethodCounter = 1
Doing something
Exiting DoSomething. MethodCounter = 1
Before method DoSomethingElse. MethodCounter = 1
Entering DoSomethingElse. MethodCounter = 2
Doing something else
Exiting DoSomethingElse. MethodCounter = 2
After method DoSomethingElse. MethodCounter = 2
Entering set_FirstName. MethodCounter = 3
Setting property: FirstName = John. MethodCounter = 4
Exiting set_FirstName. MethodCounter = 4
Entering set_LastName. MethodCounter = 5
Setting property: LastName = Smith. MethodCounter = 6
Exiting set_LastName. MethodCounter = 6


In a single aspect, we have implemented advice and pointcuts that we normally would have written three different aspects using the base classes. In addition, we were able to share state between those advices. Examine the output of the MethodCounter. It’s incrementing as we continue along with the execution. If we had three individual aspects the provided equivalent advices, the output would look more like

Entering DoSomething. MethodCounter = 1
Doing something
Exiting DoSomething. MethodCounter = 1
Before method DoSomethingElse. MethodCounter = 0
Entering DoSomethingElse. MethodCounter = 1
Doing something else
Exiting DoSomethingElse. MethodCounter = 1
After method DoSomethingElse. MethodCounter = 0
Entering set_FirstName. MethodCounter = 1
Setting property: FirstName = John. MethodCounter = 1
Exiting set_FirstName. MethodCounter = 1
Entering set_LastName. MethodCounter = 1
Setting property: LastName = Smith. MethodCounter = 1
Exiting set_LastName. MethodCounter = 1

Advices

There are a few advice attributes that you can use. Each advice attribute has a corresponding simple aspect base class and behaves in the same way.

OnMethodEntryAdvice

OnMethodSuccessAdvice

OnMethodExceptionAdvice

OnMethodExitAdvice

These advices are the equivalent to the advices in the OnMethodBoundaryAspect base class

OnMethodInvokeAdvice

These advices are the equivalent to the advices in the MethodInterceptionAspect base class

OnLocationGetValueAdvice

OnLocationSetValueAdvice

These advices are the equivalent to the advices in the LocationInterceptionAspect base class

OnEventAddHandlerAdvice

OnEventRemoveHandlerAdvice

OnEventInvokeHandlerAdvice

These advices are the equivalent to the advices in the EventInterceptionAspect base class

IntroduceMember

Introduce a method, property or event to the target class.

IntroduceInterface

Introduce a method to the target class.

When applying advice to a method, the method must be public and have the same signature as the corresponding base class advice signature. For example, in order to apply OnLocationGetValueAdvice on a method, the method must be public and have a single LocationInterceptionArgs parameter with no return value (void).

Pointcuts

A pointcut has to be defined in order to tell PostSharp where to apply the advice. You can think of pointcuts as expressions that return a set of elements of code. These elements of code must be compatible with the type of advice (for instance, do not try to add an OnLocationGetValue advice to a field). Remember that you can only add advices to code that belong to the target of the aspect. So if the aspect has been applied to a type, you can only add advices to members of this type, or to the type itself.

MulticastPointcut

A declarative pointcut that works similarly to MulticastAttribute.

MethodPointcut

An imperative pointcut: the pointcut is a method that returns an enumeration of elements of code. The method can be implemented in C#, for instance using Linq.

SelfPointcut

A pointcut that evaluates to the target of the aspect.

MulticastPointcut

MemberName

MemberName takes an expression (static name, wildcard or regular expression) to specify targets.

Targets

Targets can be set to a combination of MulticastTargets flags. For example, MulticastTargets.Method | MulticastTargets.Property to specify the targets will be methods and properties.

Attributes

Attributes is how we define the scope and visibility of the intended targets and can be set to a combination of MulticastAttributes flags. For example, we can target members that are private and static by using MulticastAttributes.Private | MulticastAttributes.Static.

MethodPointcut

MethodPointcut allows us to pass in the name of a method that PostSharp can use to get a list of targets. Let’s look at an example

[Serializable]
public class ExampleAspect : TypeLevelAspect
{
    public IEnumerable FindTargetMethods(Type target)
    {
        IEnumerable targets = target.GetMethods()
                         .Where(c => c.Name.Contains("Something"));
        return targets;
    }

    [OnMethodEntryAdvice, MethodPointcut("FindTargetMethods")]
    public void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("Entering method: " + args.Method.Name);
    }

}

Our aspect has only one advice that we want to implement, OnMethodEntryAdvice. We use the MethodPointcut attribute and pass it “FindTargetMethods” which is the name of the method we’ve setup to determine which methods will be targets. The method that is going to return the targets has to have a specific signature. PostSharp documentation defines this signature as

IEnumerable SelectCodeElements(AspectTargetType target)

AdviceTargetType will be replaced with either object or a reflection type representing the targets of the advice. For example, MethodInfo when the advice targets are methods and PropertyInfo when the targets are Properties.

AspectTargetType will be replaced with either object or a reflection type corresponding to the targets of the aspect. For example, Type for AssemblyLevelAspect, TypeLevelAspect, InstanceLevelAspect and MethodInfo for MethodLevelAspect.

If the signature is not valid for the type of aspect then PostSharp will produce a compiler error. When we apply the aspect to our example class and run the application, we see that PostSharp has applied the advice to both of the class methods.

public class Program
{
    static void Main(string[] args)
    {
        ExampleClass ec = new ExampleClass();
        ec.DoSomething();
        ec.DoSomething1();

        Console.ReadKey();
    }
}

[ExampleAspect]
public class ExampleClass
{
    public int ID { get; set; }
    public string First { get; set; }
    public string Last { get; set; }

    public void DoSomething()
    {
        this.First = "John";
        this.Last = "Smith";
    }

    public void DoSomething1()
    {
        Console.WriteLine("Did something");
    }
}

The output is

Entering method: DoSomething
Entering method: DoSomething1
Did something


SelfPointcut

There is a special attribute that you can use instead of MulticastPointcut, the SelfPointcut attribute. SelfPointcut tells PostSharp to select the exact aspect target. For example, if we have the following aspect

[Serializable]
public class ExampleAspect: MethodLevelAspect
{
    [OnMethodEntryAdvice, SelfPointcut]
    public void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("Entering " + args.Method.Name);
    }

}

When applied to a method directly, the SelfPointcut will use that exact method as the target. If applied to a class, all methods in the class will get the advice because all of the methods would be the intended target. If the aspect was applied at the assembly level with specific targets configured, then PostSharp will use those exact targets. SelfPointcut is a way to defer the selection of pointcuts to a higher mechanism.

Benefits over base classes

Base classes encapsulate a single transformation, which means if you want to apply multiple transformations, you would need to build just as many aspects. For example, if you wanted to marshal a method invocation to a different thread and log exceptions that occur in that method, you would need to build two independent aspects and apply both to the target.

Sharing state between advices in multiple independent aspects requires convoluted mechanics. Building complex aspects using advices and pointcuts provides the benefit of sharing state between advices.

Conclusion

By now you should have a clear understanding of how to build complex aspects using the tools provided by PostSharp. The term “complex” shouldn’t be a deterrent because over the last two days we’ve seen just how easy it is to build aspects.

self573_thumb[1]Dustin Davis Davis is an enterprise solutions developer and regularly speaks at user groups and code camps. He can be followed on Twitter @PrgrmrsUnlmtd or his blog Programmers-Unlimited.com

When it comes to building aspects, using the provided base classes such as OnMethodBoundaryAspect and LocationInterceptionAspect are quick and easy ways to implement a simple aspect. But they have their limitations: each aspect can only implement one transformation. But what if you need to encapsulate a design pattern made of several transformations? We’re going to look at two ways of building complex aspects: aspect providers today and advices tomorrow.

Base Aspect Classes

There are different base classes in which an aspect can derive from. For example, OnMethodBoundaryAspect is derived from MethodLevelAspect class because it deals with methods. These base classes are pre-configured for MulticastAttributeUsage and pre-implemented interfaces. These classes are just containers for behaviors, they do not implement any behavior themselves, but it’s important to choose the correct class when developing aspects.

· AssemblyLevelAspect – Base class for all aspects applied on assemblies

· TypeLevelAspect – Base class for all aspects applied on types

· MethodLevelAspect – Base class for all aspects applied on methods

· LocationLevelAspect – Base class for all aspects applied on fields, properties or parameters

· EventLevelAspect – Base class for all aspects applied on events

PostSharp does not rely on these classes, but on the interfaces they implement. You can create your own aspect classes by implementing the right interface – for instance IOnMethodBoundaryAspect.

IAspectProvider

When an aspect implements the IAspectProvider interface, it can provide additional aspects dynamically. Simply put, an aspect can tell PostSharp to apply other aspects to a target at compile time. Let’s take a look at an example

[Serializable]
public class Aspect1 : IOnMethodBoundaryAspect
{
    public void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("Aspect1: OnEntry for " + args.Method.Name);
    }

    public void OnException(MethodExecutionArgs args) { }
    public void OnExit(MethodExecutionArgs args) { }
    public void OnSuccess(MethodExecutionArgs args) { }
    public void RuntimeInitialize(MethodBase method) { }
}

[Serializable]
public class Aspect2 : IMethodInterceptionAspect
{
    public void OnInvoke(MethodInterceptionArgs args)
    {
        Console.WriteLine("Aspect2: OnInvoke for " + args.Method.Name);
        args.Proceed();
    }

    public void RuntimeInitialize(MethodBase method) { }
}

[Serializable]
public class ComplexAspect : MethodLevelAspect, IAspectProvider
{
    private readonly Aspect1 _aspect1 = new Aspect1();
    private readonly Aspect2 _aspect2 = new Aspect2();

    #region IAspectProvider Members

    public IEnumerable ProvideAspects(object targetElement)
    {
        MemberInfo nfo = (MemberInfo)targetElement;

        yield return new AspectInstance(targetElement, _aspect1);
            
        if (nfo.ReflectedType.IsPublic && !nfo.Name.Equals(".ctor"))
        {
            yield return new AspectInstance(targetElement, _aspect2);
        }
    }

    #endregion
}

We have three aspects here. Aspect1 and Aspect2 are just simple aspects that write their status to the console. The ComplexAspect however has some work happening. ComplexAspect derives from MethodLevelAspect because we’re going to be dealing with methods and it implements the IAspectProvider interface because we want to dynamically determine which aspects to apply to each target method.

We start by defining two instances one for Aspect1 and another for Aspect2. We’ll use these inside of ProvideAspects method when telling PostSharp to apply them to a target.

Inside the ProvideAspect method we cast targetElement as a MemberInfo structure. If we were using a LocationLevelAspect class instead of MethodLevelAspect class, we would cast it as a LocationInfo instead of MemberInfo because we’re working on a different level.

Since the return type of ProvideAspects method is an IEnumerable, we can use a yield return to provide aspects for PostSharp to apply. We return the instance of Aspect1 for all targets and then we check to see if the target method is public and is not a constructor. If it meets the criteria then we return the instance of Aspect2.

public class Program
{
    static void Main(string[] args)
    {
        ExampleClass ec = new ExampleClass();
        ec.DoSomething();

        Console.ReadKey();
    }
}

[ComplexAspect]
public class ExampleClass
{
    public void DoSomething()
    {
        Console.WriteLine("Did something");
    }
}

Our test code is a simple class with the ComplexAspect applied to it and we’re just making a call to DoSomething. The output is

Aspect1: OnEntry for .ctor
Aspect1: OnEntry for DoSomething
Aspect2: OnInvoke for DoSomething
Did something

Aspect1 was applied to both the constructor and the DoSomething method while Aspect2 was only applied to the DoSomething method.

CustomAttributeIntroductionAspect

PostSharp provides us with a very nice aspect that we can use to apply custom attributes to targets. For example, we can apply attributes to a DTO for XmlSerialization. Let’s look at an example of that

[AddXmlIgnoreAttribute]
public class ExampleClass
{
    [XmlElement]
    public int ID { get; set; }
    public string First { get; set; }
    public string Last { get; set; }
}

[MulticastAttributeUsage(MulticastTargets.Field | MulticastTargets.Property,
TargetMemberAttributes = MulticastAttributes.Public | MulticastAttributes.Instance)] public sealed class AddXmlIgnoreAttribute : LocationLevelAspect, IAspectProvider { private static readonly CustomAttributeIntroductionAspect
customAttributeIntroductionAspect = new CustomAttributeIntroductionAspect( new ObjectConstruction(typeof(XmlIgnoreAttribute)
.GetConstructor(Type.EmptyTypes))); public IEnumerable ProvideAspects(object targetElement) { LocationInfo memberInfo = (LocationInfo)targetElement; if (memberInfo.PropertyInfo.IsDefined(typeof(XmlElementAttribute), false) || memberInfo.PropertyInfo.IsDefined(typeof(XmlAttributeAttribute), false)) yield break; yield return new AspectInstance(memberInfo.PropertyInfo,
customAttributeIntroductionAspect); } }

We define a simple DTO class with four public properties. The DTO class is decorated with the AddXmlIgnoreAttribute.

Our aspect is going to apply the [XmlIgnore] attribute to all members of the DTO. If a member is already marked for inclusion using the [XmlElement] or [XmlAttribute] attributes then it will not receive the [XmlIgnore] attribute.

We start by defining the multicast options for our aspect. We tell PostSharp to only apply the aspect to fields or properties that are public and not static. Our aspect derives from LocationLevelAspect so we receive information structures specific to fields, properties and parameters.

We declare a new instance of CustomAttributeIntroductionAspect and passing in the ObjectConstruction data for XmlIgnoreAttribute type using the default empty constructor for it. We’ll cover how CustomAttributeIntroductionAspect works internally at another time.

In the ProvideAspects method, we cast targetElement as LocationInfo so we can work with the reflection info for the target. We use the LocationInfo to check if the property has either of the xml attributes defined on it and if it does we don’t yield any results for that target. Otherwise, we pass back to PostSharp the instance of CustomAttributeIntroductionAspect for application on the target.

Using ILSpy to see the end result

image

Except for ID, all of the properties now have XmlIgnore attributes.

Conclusion

IAspectProvider is a great way to dynamically determine which aspects to apply to a target at compile time but because aspects are still independent of each other, it has its limitations. Tomorrow we’re going to look at another way to build complex aspects.
self573_thumb[1]Dustin Davis Davis is an enterprise solutions developer and regularly speaks at user groups and code camps. He can be followed on Twitter @PrgrmrsUnlmtd or his blog Programmers-Unlimited.com

Previously we’ve covered interception for methods and “locations” (fields/properties). Today we’re going to finish up interception by looking at the EventInterceptionAspect.

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.

image

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.

Conclusion

The further we dig, the more we see just how comprehensive PostSharp really is. We still have more to cover. The topics are getting more complex, but I’ll try to make sense of them.
self573_thumb[1]Dustin Davis Davis is an enterprise solutions developer and regularly speaks at user groups and code camps. He can be followed on Twitter @PrgrmrsUnlmtd or his blog Programmers-Unlimited.com