If you have not already read my article Introduction to Aspect Oriented Programming and PostSharp then I suggest you start there.

PostSharp has a community edition and a professional edition. The community edition lacks a few features such as event-level aspects and the visual studio add-ins but for most it’s just enough to get the job done.

If the community edition is free and you’re never going to use the missing features, why bother paying for the professional edition? Performance. PostSharp, with a professional license, goes the extra step at build time to do ‘smart’ optimizations.

We’re going to examine the difference of just switching to a professional license. We’ll build a simple lazy loading aspect and check out what PostSharp does to our code when we build using both a community license and a professional license.

Simple Project

Just add these files to a console application project. The LazyLoadAspect just checks the current value of the property and if it’s null then it sets it to a new instance.

SimpleClass1.cs

[LazyLoadingAspect]
class SimpleClass1
    {
        public List MyProperty { get; set; }
        public SimpleClass1()
        {
            MyProperty.Add("test");
        }
    }

LazyLoadingAspect.cs

[Serializable]
    [LazyLoadingAspect(AttributeExclude=true)]
    [MulticastAttributeUsage(MulticastTargets.Property)]
    public class LazyLoadingAspectAttribute : LocationInterceptionAspect
    {
        public override void OnGetValue(LocationInterceptionArgs args)
        {
            if (args.GetCurrentValue() != null)
            {
                args.ProceedGetValue(); return;
            }

            Type t = args.Location.PropertyInfo.PropertyType;

            args.SetNewValue(Activator.CreateInstance(t));
            args.ProceedGetValue();
        }

    }

Once that’s squared away, we build the project using a community license and then build again using a professional license. After the code is built, we can use reflector to see the results.

Community license version

internal class SimpleClass1
{
    // Methods
    [CompilerGenerated]
    static SimpleClass1()
    {
        <>z__Aspects.Initialize();
    }

    public SimpleClass1()
    {
        this.MyProperty.Add("test");
    }

    // Properties
    public List MyProperty
    {
        [CompilerGenerated]
        get
        {
            LocationInterceptionArgsImpl> CS$0$3 = new LocationInterceptionArgsImpl>(this, Arguments.Empty) {
                Location = <>z__Aspects.l1,
                TypedBinding = c__Binding.singleton,
                LocationName = "MyProperty",
                LocationFullName = "LazyLoadingAspect.SimpleClass1.MyProperty"
            };
            <>z__Aspects.a0.OnGetValue(CS$0$3);
            return CS$0$3.TypedValue;
        }
        [CompilerGenerated]
        set
        {
            LocationInterceptionArgsImpl> CS$0$1 = new LocationInterceptionArgsImpl>(this, Arguments.Empty) {
                Location = <>z__Aspects.l1,
                TypedBinding = c__Binding.singleton,
                TypedValue = value,
                LocationName = "MyProperty",
                LocationFullName = "LazyLoadingAspect.SimpleClass1.MyProperty"
            };
            <>z__Aspects.a0.OnSetValue(CS$0$1);
        }
    }

    // Nested Types
    [CompilerGenerated, DebuggerNonUserCode]
    internal sealed class <>z__Aspects
    {
        // Fields
        internal static readonly LazyLoadingAspectAttribute a0 = ((LazyLoadingAspectAttribute) <>z__AspectsImplementationDetails.aspects1[0]);
        internal static LocationInfo l1 = new LocationInfo(typeof(SimpleClass1).GetProperty("MyProperty", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));

        // Methods
        [CompilerGenerated]
        static <>z__Aspects()
        {
            a0.RuntimeInitialize(l1);
        }

        public static void Initialize()
        {
        }
    }

    [CompilerGenerated]
    private sealed class c__Binding : LocationBinding>
    {
        // Fields
        public static SimpleClass1.c__Binding singleton = new SimpleClass1.c__Binding();

        // Methods
        [DebuggerNonUserCode]
        private c__Binding()
        {
        }

        public override List GetValue(ref object instance, Arguments index, object aspectArgs)
        {
            SimpleClass1@this = (SimpleClass1) instance;
            return @this.k__BackingField;
        }

        public override void SetValue(ref object instance, Arguments index, List value, object aspectArgs)
        {
            SimpleClass1@this = (SimpleClass1) instance;
            List value = value;
            @this.k__BackingField = value;
        }
    }
}

Professional license version

internal class SimpleClass1
{
    // Methods
    public SimpleClass1()
    {
        this.MyProperty.Add("test");
    }

    private void (List value)
    {
        this.k__BackingField = value;
    }

    // Properties
    public List MyProperty
    {
        [CompilerGenerated]
        get
        {
            LocationInterceptionArgsImpl> args = new LocationInterceptionArgsImpl>(this, Arguments.Empty) {
                Location = <>z__Aspects.l1,
                TypedBinding = c__Binding.singleton
            };
            <>z__Aspects.a0.OnGetValue(args);
            return args.TypedValue;
        }
        [CompilerGenerated]
        set
        {
            this.(value);
        }
    }

    // Nested Types
    [CompilerGenerated, DebuggerNonUserCode]
    internal sealed class <>z__Aspects
    {
        // Fields
        internal static readonly LazyLoadingAspectAttribute a0 = ((LazyLoadingAspectAttribute) <>z__AspectsImplementationDetails.aspects1[0]);
        internal static LocationInfo l1 = new LocationInfo(typeof(SimpleClass1).GetProperty("MyProperty", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
    }

    [CompilerGenerated]
    private sealed class c__Binding : LocationBinding>
    {
        // Fields
        public static SimpleClass1.c__Binding singleton = new SimpleClass1.c__Binding();

        // Methods
        [DebuggerNonUserCode]
        private c__Binding()
        {
        }

        public override List GetValue(ref object instance, Arguments index, object aspectArgs)
        {
            SimpleClass1 class2 = (SimpleClass1) instance;
            return class2.k__BackingField;
        }

        public override void SetValue(ref object instance, Arguments index, List value, object aspectArgs)
        {
            ((SimpleClass1) instance).(value);
        }
    }
}

What are the differences?

In both versions you end up with two new nested internal sealed classes within SimpleClass1: <>z__Aspects and <MyProperty>c__Binding but they differ slightly in each version (see below).

SimpleClass1
CL (Community License) adds a static constructor which calls the static Initialize method on the <>z__Aspects class. PL (Professional License) adds a method <set_MyProperty>(List value), which is similar to what you would normally see on a set method for a property, and it just does a set operation on the backing field for MyProperty.

The biggest difference is the setter/getter for MyProperty. Our aspect was designed to work only on the getter but the CL version has added code in the setter as well as the getter.

PostSharp does ‘smart’ optimizations when using a professional license which includes only putting code where it needs to be which is why there is no additional code in the setter in the PL version. What does this really mean? Since our aspect is derived from the base aspect LocationInterceptionAspect and the OnSetValue method is virtual in the base so our aspect implicitly implements the OnSetValue method. Basically the CL version didn’t bother to see that we are not explicitly implementing the OnSetValue method in our aspect so it added code as if we were. The PL version will look at what we explicitly implemented to perform optimizations. 

The getter in both versions initialize a LocationInterceptionArgsImpl<> which it passes to the OnGetValue method but the CL version includes setting LocationName and LocationFullName. We’ll see why later on.

Notice that the setter in the CL version calls the OnSetValue method of the aspect instance where the PL version calls the local class method. Investigating further, the CL version calls OnSetValue from the base class LocationInterceptionAspect. The PL version knows that we did not implement OnSetValue on our aspect so it does not generate those calls.

<>z__Aspects
CL has an additional Initialize static method which is called but does nothing. CL also has a static constructor which makes a call to a0.RuntimeInitialize method which is empty because we did not implement it. This is because of the implicit implementation from the base class. The PL version sees that we didn’t implement any of this in our aspect so it leaves the unnecessary code out.

<MyProperty>c__Binding
Except for the SetValue method, this class is pretty much the same in both versions. Notice that the GetValue methods only differ by variable names. The CL version uses @this where the PL version uses class2. Why it’s this way, I don’t know.

The CL version of SetValue method directly sets the backing field with the value where the PL version makes a call back to the <set_MyProperty> method on the instance of SimpleClass1. Both versions set the backing field but the PL version skips the creation of an addition List<> variable. Notice that the PL version doesn’t call the SetValue method at all.

LazyLoadingAspect
Looking at the code for our aspect, you can see why the CL version was setting the LocationFullName and LocationName in the initialization of the LocationInterceptionArgsImpl<> and the PL version does not. The OnGetValue method in our aspect has been decorated with the [LocationInterceptionAdviceOptimization] attribute specifying LocationInterceptionAdviceOptimizations.IgnoreGetLocationFullName and LocationInterceptionAdviceOptimizations.IgnoreGetLocationName flags because we’re not referencing those variables in the body of our OnGetValue method. If we change the code to use LocationName then the flag would not be set on the attribute.

Benchmarks

Since this is such a simple example, the number and types of optimizations are going to be minimal, but they’re still there. So what do they amount to? Let’s look at some numbers.

These benchmarks were done using RedGate Performance Profiler 6 and the following code in a console application project with .NET 3.5 as the target framework.

class Program
    {
        static void Main(string[] args)
        {
            SimpleClass1 x = new SimpleClass1();

            for (int i = 0; i < 1000000; i++)
            {
                List tmp = x.MyProperty;

            }
        }
    }
Results Community Professional
Program.Main() 422ms 231ms
LazyLoadingAspectAttribute.OnGetValue() 151ms 78ms

Almost a 50% reduction in execution time for this test.

Conclusion

AOP is a great methodology to implement in your designs and PostSharp is the leading AOP framework. Even if you don’t want to purchase a professional license, you can still reap the benefits of PostSharp using the community edition, but you’re missing out on some great features.

Advertisements