Archive

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

Continuing from yesterday, we’re covering the lifetime and scope of aspects. Today we’ll look at the scope of aspects.

Scope

Aspects come in two scopes, static and instance. Static scoped aspects are created and initialized at compile time for consumption at runtime using a singleton pattern. Instance scoped aspects are a bit different. Instanced scoped aspects use a prototype pattern by creating and initializing the aspect at compile time, but at run time when a new instance of a target member’s declaring type is created, a new instance of the aspect is created and used.

Static scoped aspects have the same lifetime as the application while instance scoped aspects have the same lifetime as the instance of the type the aspect was applied to.

However, no matter which scope an aspect will have, PostSharp creates an instance of the aspect for each target in which the aspect has been applied. No doubt this is all confusing so let's see what's going on with a demo.

Aspect demo

To make sense of all of this, let's start with a basic aspect.

[Serializable]
public class TestAspect : LocationInterceptionAspect
{
    private string InstID;
    private string aspectID;
    private string source;
    private int getCount = 0;

    public override void CompileTimeInitialize(LocationInfo targetLocation, 
                                                                 AspectInfo aspectInfo)
    {
        source = targetLocation.DeclaringType.Name + "." + targetLocation.Name 
                             + " (" + targetLocation.LocationType.Name + ")";
        aspectID = Guid.NewGuid().ToString();
    }

    public override void RuntimeInitialize(LocationInfo locationInfo)
    {
        InstID = Guid.NewGuid().ToString();
    }

    public override void OnGetValue(LocationInterceptionArgs args)
    {
        getCount++;

        Console.WriteLine(source + "\n\tInstance: " + this.InstID + "\n\tAspect: " +
                                             aspectID + "\n\tCount: " + getCount);
        args.ProceedGetValue();
    }
}

In CompileTimeInitialize we setup our source which is a combination of declaring type, target name and then the target's type. Then we assign a guid to aspectID which we'll use when we evaluate the aspect's life. In RunTimeInitialize we do the same thing and create a new guid that we use for the instance id. Then in the OnGetValue method we just increment the count and print all of our data to the console before calling ProceedGetvalue.

Our example class is as follows

class TestClass
{
    [TestAspect]
    public int MyField1;

    [TestAspect]
    public int MyField2;

    [TestAspect]
    public static int MyField3;

    public TestClass()
    {
        MyField1 = 1;
        MyField2 = 2;
    }

    static TestClass()
    {
        MyField3 = 10;
    }
}

And our test code

int val = 0;
for (int i = 1; i <= 2; i++)
{
    Console.WriteLine("--- PASS {0} ---", i);

    TestClass tc1 = new TestClass();
    TestClass tc2 = new TestClass();

    val = TestClass.MyField3;
                
    Console.WriteLine();
    val = tc1.MyField1;
    val = tc1.MyField2;

    Console.WriteLine();
    val = tc2.MyField1;
    val = tc2.MyField2;
                
    Console.WriteLine();
}

What we're doing is creating two instances of our TestClass and then getting the value of Myfield1 and MyField2 from each instance which our aspect will intercept and provide us with information about which aspect is handling our request.

Aspect for each target

When we run our test code, we get the following output (I’ve put the two passes side by side for comparison)

--- PASS 1 ---
TestClass.MyField3 (Int32)
Instance: d6c15b92-3ba1-4aba-8325-1ca07d50979c
Aspect: b73e55cf-6568-4b1c-9465-8d3b77f3288c
Count: 1

TestClass.MyField1 (Int32)
Instance: 667d5f03-ac25-43ef-86dd-7aa88ccc46c7
Aspect: a6e612ed-7ab9-4f31-956d-6be2f3198352
Count: 1

TestClass.MyField2 (Int32)
Instance: a5efc352-31f0-430c-8153-d78e0e1453c4
Aspect: 8feb7451-5df2-48e5-b57d-3588456aca96
Count: 1

TestClass.MyField1 (Int32)
Instance: 667d5f03-ac25-43ef-86dd-7aa88ccc46c7
Aspect: a6e612ed-7ab9-4f31-956d-6be2f3198352
Count: 2

TestClass.MyField2 (Int32)
Instance: a5efc352-31f0-430c-8153-d78e0e1453c4
Aspect: 8feb7451-5df2-48e5-b57d-3588456aca96
Count: 2
--- PASS 2 ---
TestClass.MyField3 (Int32)
Instance: d6c15b92-3ba1-4aba-8325-1ca07d50979c
Aspect: b73e55cf-6568-4b1c-9465-8d3b77f3288c
Count: 2

TestClass.MyField1 (Int32)
Instance: 667d5f03-ac25-43ef-86dd-7aa88ccc46c7
Aspect: a6e612ed-7ab9-4f31-956d-6be2f3198352
Count: 3

TestClass.MyField2 (Int32)
Instance: a5efc352-31f0-430c-8153-d78e0e1453c4
Aspect: 8feb7451-5df2-48e5-b57d-3588456aca96
Count: 3

TestClass.MyField1 (Int32)
Instance: 667d5f03-ac25-43ef-86dd-7aa88ccc46c7
Aspect: a6e612ed-7ab9-4f31-956d-6be2f3198352
Count: 4

TestClass.MyField2 (Int32)
Instance: a5efc352-31f0-430c-8153-d78e0e1453c4
Aspect: 8feb7451-5df2-48e5-b57d-3588456aca96
Count: 4

Let's break it down. In Pass 1 we see 5 calls made to our aspect. The first call is to the static member MyField3 while the next 2 are instance calls to MyField1 and MyField3 on tc1 instance and the same for the next 2 calls, but for the tc2 instance.

If you compare the aspect ID's, there are actually only 3 different instances of our aspect, one for each of the properties we applied it to, MyField1, MyField2 and MyField3 even though we have two separate instances of TestClass.

Static Scoped

Now that we've identified that a new aspect is generated for each target, let's examine the Instance ID's. In Pass 1 we see there are 3 instance ID's. Examining Pass 2, we see those exact same instance ID's even though we created new instances of our class.

Notice how the counter is increasing. In Pass 1 the first call to tc1.MyField1 results in 1 while the call to tc2.MyField1 results in a 2. In Pass 2 we see that the trend continues. This is due to the static nature of the aspect instances.

Instance Scoped

Aspects are only instance scoped when implementing the IInstanceScopedAspect interface or inheriting from InstanceLevelAspect, so let's update our aspect.

[Serializable]
public class TestAspect : LocationInterceptionAspect, IInstanceScopedAspect
{
    private string InstID;
    private string aspectID;
    private string source;
    private int getCount = 0;

    public override void CompileTimeInitialize(LocationInfo targetLocation, 
                                                                 AspectInfo aspectInfo)
    {
        source = targetLocation.DeclaringType.Name + "." + targetLocation.Name 
                      + " (" + targetLocation.LocationType.Name + ")";
        aspectID = Guid.NewGuid().ToString();
    }

    public override void RuntimeInitialize(LocationInfo locationInfo)
    {
        InstID = Guid.NewGuid().ToString();
    }

    public override void OnGetValue(LocationInterceptionArgs args)
    {
        getCount++;

        Console.WriteLine(source + "\n\tInstance: " + this.InstID 
                 + "\n\tAspect: " + aspectID + "\n\tCount: " + getCount);
        args.ProceedGetValue();
    }


    #region IInstanceScopedAspect Members

    public object CreateInstance(AdviceArgs adviceArgs)
    {
        return this.MemberwiseClone();
    }

    public void RuntimeInitializeInstance()
    {
        InstID = Guid.NewGuid().ToString();
    }

    #endregion
}

Now we implement IInstanceScopedAspect which requires CreateInstance() and RuntimeInitializeInstance(). CreateInstance is called to create a new instance of the aspect based on the current instance, thus using the current instance as a protoype. All we need to do is use the MemberwiseClone() and we're set.

RuntimeInitializeInstance is where we update our instance ID. If we didn't, we would only get the instance ID specified in the RuntimeInitialize which is only invoked once for each aspect when it's deserialized, not when a new instance is created.

Let's run our test code and see how things have changed. (I’ve put the two passes side by side for comparison)

--- PASS 1 ---
TestClass.MyField3 (Int32)
Instance: ea470da4-ed8d-49e1-be96-5ae16ac000a6
Aspect: b6aa3c5c-3868-40e8-af37-70234032d734
Count: 1

TestClass.MyField1 (Int32)
Instance: a0b96c14-0d81-463f-9374-6915b6def2a0
Aspect: a975c1ce-6839-454c-b054-14b185fd29a8
Count: 1

TestClass.MyField2 (Int32)
Instance: 3a6676c6-0325-4ee3-8dbb-fe4d6c93acdd
Aspect: dbd6865f-3b5b-48dd-a87e-ef8a89695e2f
Count: 1

TestClass.MyField1 (Int32)
Instance: 3ee8689a-a1a9-4dfc-9ca6-10bd50129a1d
Aspect: a975c1ce-6839-454c-b054-14b185fd29a8
Count: 1

TestClass.MyField2 (Int32)
Instance: 84f63d43-c91a-46ba-8bda-89ad16d0ba2c
Aspect: dbd6865f-3b5b-48dd-a87e-ef8a89695e2f
Count: 1
--- PASS 2 ---
TestClass.MyField3 (Int32)
Instance: ea470da4-ed8d-49e1-be96-5ae16ac000a6
Aspect: b6aa3c5c-3868-40e8-af37-70234032d734
Count: 2

TestClass.MyField1 (Int32)
Instance: 2ad257df-682e-4652-98fa-5466d2bb08aa
Aspect: a975c1ce-6839-454c-b054-14b185fd29a8
Count: 1

TestClass.MyField2 (Int32)
Instance: e39fe1e1-ca7a-4b26-90ac-8c81fc1aafe5
Aspect: dbd6865f-3b5b-48dd-a87e-ef8a89695e2f
Count: 1

TestClass.MyField1 (Int32)
Instance: cb40067e-2b69-4c5b-814e-4a7420a085a9
Aspect: a975c1ce-6839-454c-b054-14b185fd29a8
Count: 1

TestClass.MyField2 (Int32)
Instance: e4c21efc-92e6-41cc-bf34-babbddf4ea48
Aspect: dbd6865f-3b5b-48dd-a87e-ef8a89695e2f
Count: 1

First, have a look at the Aspect ID's. Notice that again, there are only 3 ID's as we only have 3 targets. They are used both in Pass 1 and Pass 2. This is the same as the static scoped example. The difference is the instance ID's. Except for TestClass.Myfield3, which is static, there are no repeating instance ID's. For each target, we get a new instance of the aspect when we instantiated a new instance of TestClass. This can be verified by examining the count. Since we only make one get call per TestClass instance, count is always 1 because it's scoped to the current instance of the declaring type, tc1 and tc2.

So what happened with TestClass.MyField3? Even though the aspect implements IInstanceScopedAspect, when applied to a static target, the aspect instance becomes static scoped. This only makes sense considering the nature of static members.

Conclusion

It's amazing how much flexibility PostSharp gives us, but as I've stated before, a solid understanding of how it works is key to producing quality results. This week we spent a lot of time under the hood looking at what PostSharp does when you click the build button.

 

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