Archive

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

Aspects have a lifetime as well as a scope just like classes in your project. Understanding this cycle is important for producing quality aspects as well as getting aspects to do what you need them to do. Today we’re going to cover the lifecycle.

Aspect Lifetime

So far we’ve built aspects and we’ve seen the results in action. What we haven’t seen is what happens between those two points. After the build process has been completed by MSBuild, PostSharp starts up and starts processing our aspects and aspect declarations.

Aspects go through two phases, compile time initialization and run time initialization.

Note: For Silverlight, Windows Phone and .NET CF, aspects are initialized at run time and all compile time steps are skipped.

Compile time

One of the benefits of using PostSharp is that it initializes aspects at compile time. Any expensive work that needs to be done related to the aspect can be done at compile time instead of run time. The build process may take a hit on performance, but run time does not suffer.

A new aspect instance is created for every target to which the aspect is applied. Each aspect goes through compile time validation and initialization. Aspect instances are then serialized into a resource for consumption at run time.

Compile time Validation

Last week we covered multicasting which is a way to apply an aspect to multiple targets. Even though multicasting gives us flexibility in applying aspects to targets, there are times when application of an aspect to a target must be considered using logic.

All aspects have a CompileTimeValidate virtual method. When implemented in the aspect, PostSharp will call this method to determine if the application of the aspect should continue on the given target. Depending on the type of aspect being implemented, CompileTimeValidate parameter(s) will be reflection information about the current target PostSharp is asking about. For example, Method based aspects like OnExceptionAspect and OnMethodBoundaryAspect will have a parameter of type MethodBase while LocationInterceptionAspect will have a parameter of type LocationInfo. If the aspect has been applied on an invalid target, implementations of this method must return false so this target will be silently ignored. Implementations can emit errors and warning by using Message.Write.

Compile time Initialization

All aspects also have a CompileTimeInitialize virtual method that can be implemented to perform expensive operations and/or initialize serializable fields so they are available at run time. PostSharp provides CompileTimeInitialize with the reflection information about the current target as well as information about the current aspect. Remember that since this is compile time, you won’t have access to the actual instance of the targets.

Run time

Before any aspect can be executed, PostSharp has to deserialize the aspects and initialize them. Since the serialization process uses a binary serializer, the aspect’s constructor is not called. The only way to perform initialization tasks at run time is to implement the RunTimeInitialize virtual method. When implementing RunTimeInitialize, you have access to the reflection information for the target but unlike CompileTimeInitialize, you have access to the instance of the target, not just the Meta data.

Example: Caching

We’re going to borrow the caching aspect from Matthew Groove’s post 5 Ways That Postsharp Can SOLIDify Your Code: Caching for this example.

[Serializable]
public class CacheAttribute : MethodInterceptionAspect
{
    [NonSerialized]
    private static readonly ICache _cache;
    private string _methodName;

    static CacheAttribute()
    {
        if (!PostSharpEnvironment.IsPostSharpRunning)
        {
            // one minute cache
            _cache = new StaticMemoryCache(new TimeSpan(0, 1, 0));
            // use an IoC container/service locator here in practice
        }
    }

    public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
    {
        _methodName = string.Format("{0}.{1}", method.DeclaringType.Name, method.Name);
    }

    public override void OnInvoke(MethodInterceptionArgs args)
    {
        var key = BuildCacheKey(args.Arguments);
        if (_cache[key] != null)
        {
            args.ReturnValue = _cache[key];
        }
        else
        {
            var returnVal = args.Invoke(args.Arguments);
            args.ReturnValue = returnVal;
            _cache[key] = returnVal;
        }
    }

    private string BuildCacheKey(Arguments arguments)
    {
        var sb = new StringBuilder();
        sb.Append(_methodName);
        foreach (object argument in arguments.ToArray())
        {
            sb.Append(argument == null ? "_" : argument.ToString());
        }
        return sb.ToString();
    }
}

Notice the _cache field is marked as NonSerialized. Fields that are not initialized at compile time or are not serializable should be decorated with NonSerializable. This aspect has a static constructor to instantiate _cache to a new instance of StaticMemoryCache. Static constructors are called even when doing binary serialization.

CompileTimeInitialize is where the cache key prefix is created. In this case it’s just the method name for the target. The values stored in fields/properties at compile time will be serialized into the aspect for consumption at runtime.

The OnInvoke method starts out by building a cache key which consists of the method name of the target method (set at compile time) and the value of each method argument. If the cache contains a valid object for the generated key then the value is returned from cache. If not, then the method is invoked and the return value is stored in cache.

A caching aspect is beneficial on expensive operations such as complex computations or frequently accessed data from a database. We’ll use it on our MD5 hash computation method

class Program
{
    static void Main(string[] args)
    {
        TestClass tc = new TestClass();
        Console.WriteLine(tc.GetMD5Hash("PostSharp"));
        Console.WriteLine(tc.GetMD5Hash("SharpCrafters"));
        Console.WriteLine(tc.GetMD5Hash("PostSharp"));
        Console.WriteLine(tc.GetMD5Hash("SharpCrafters"));

        Console.ReadKey();

    }
}

class TestClass
    {
        public TestClass() { }

        [Cache]
        public string GetMD5Hash(string value)
        {
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(value);
            byte[] hash = md5.ComputeHash(inputBytes);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("X2"));
            }
            return sb.ToString();
        }
            
    }

The first two calls will result in a computation but the second two will return the value from cache without the original method being called.

Just for fun, set break points on the CompileTimeValidate and CompileTimeInitialize methods. Notice they are never hit even if you run the application. Let’s have a look at what happened after we compiled.

No aspects applied

Aspects applied

image image

Looking at the compiled executable with ILSpy, we can see that compiling with our aspect applied produces a Resources node on the namespace tree which contains a binary file. Remember, in CompileTimeInitialize the private field _methodName was populated with the target method name. If you browse around ILSpy you won’t find any trace of that value. If you look at the binary file under Resources however, we see that the value was serialized along with the aspect instance.

image

PostSharp has initialized the instances of the aspect for each target, and then serialized them into a resource for consumption at run time.

Conclusion

You should now have a good idea about the process between writing an aspect and seeing it in action. Understanding the process of how an aspect is initialized is important when rolling custom aspects. Today we only covered the first half. Tomorrow we’re going to cover aspect scope.

 

self573_thumb[1]Dustin 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

Download demo project source code

Today we jump face first into the decompiled code of interception aspects. If you have not yet read the previous post (part 1), then I suggest doing so before continuing. We’re going to look at the IoCResolution aspect we built yesterday and finish off with chaining.

Under the hood

Using ILSpy, open the PostSharpDemo1.exe and browse to the contactRepository field under ContactManager.

image

We see a getter and a setter even though this was a field and not a property. The setter contains the standard code to set the backing to the value passed in. There is no change since we did not implement OnSetValue in our aspect.

The getter starts off by instantiating a generic instance of LocationInterceptionArgs that will be passed to our aspect’s OnGetValue method. Next it sets the Location property to the LocationInfo held in <>z_Aspects.l7. We saw this in the MethodInterceptionAspect except it was a MethodInfo type instead of LocationInfo. <>z_Aspects.l7 is specifically for the ContactManager.contactRepository field. TypeBinding is then set to the singleton instance of the binding PostSharp has created for contactRepository, ContactManager.<contactRepository>c_Binding.

The TypeBinding is necessary because the LocationInterceptionArgs contain the ProceedSetValue, ProceedGetValue, GetCurrentValue and SetNewValue methods which use the binding to access the field. We use these methods to manipulate the field/property from our aspect.

image

Finally, our aspect’s OnGetValue method is called passing in the LocationInterceptionArgs and then the value to returned using the LocationInterceptionArgs.TypedValue. TypedValue will always be of the generic type used to instantiate LocationInterceptionArgs.

Chaining

When it comes to interception aspects, we have to remember how they work because when applying multiple interception aspects to the same target, a chain is created. Each interception aspect is a node on the invocation chain which means you may or may not be invoking the actual target when proceeding. If we were to apply both a location interception aspect and a method interception aspect to a single target, the chain would become

image

Each aspect intercepts the preceding aspect. The original call ends up invoking the last applied aspect. It’s important to understand this when applying aspects that change the invocation as many things can be affected such as performance and behavior.

Note that the ordering of the MethodInterceptionAspect and the LocationInterceptionAspect may be inversed. We’ll see another day how to specify the order of application of several aspects when they are applied to the same element of code.

Conclusion

It may seem overwhelming at first, but once you understand how PostSharp produces its results it’s easy to grasp what’s going on when troubleshooting. Thankfully PostSharp is a mature framework and there are little, if any, side effects.

 

self573_thumb[1]Dustin 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