Archive

As we’re progressing in the RC cycle of PostSharp 4.0, I would like to shed some light on a feature that has been often requested in the past, and is finally available in PostSharp 4.0: OnInstanceConstructedAdvice.

The objective of this advice is to provide a way for the aspect to execute code when instances of the target class are “fully constructed”. What does that mean to be “fully constructed”? In short, it means that all instance constructors in the chain have been invoked. Note that it is not the same as having an OnSuccess advice on the public constructor: such advice would be executed even if the current constructor is not the last in the chain.

Example

Let’s see this on a simple example.Here’s an aspect that has both kinds of advices:

[PSerializable]
class MyAspect : InstanceLevelAspect
{
    private Type appliedTo;

    public override void CompileTimeInitialize(Type type, AspectInfo aspectInfo)
    {
        this.appliedTo = type;
    }

    [OnInstanceConstructedAdvice]
    public void OnInstanceConstructed()
    {
        Console.WriteLine("OnInstanceConstructed({0})", this.appliedTo);
    }

    [OnMethodSuccessAdvice, MulticastPointcut(MemberName = ".ctor")]
    public void OnSuccess(MethodExecutionArgs args)
    {
        Console.WriteLine("OnSuccess({0})", this.appliedTo);
    }
}

And here is some testing code:

[MyAspect]
class Foo
{
    public Foo()
    {
Console.WriteLine("Foo()"); } public Foo(int foo) : this() {
Console.WriteLine("Foo(int)"); } } static class Program { public static void Main() { Foo foo = new Foo(0); } }

The program is instantiating Foo by invoking a constructor that chains another constructor. Its output is the following:

Foo()
OnSuccess(ConsoleApplication7.Foo)
Foo(int)
OnSuccess(ConsoleApplication7.Foo)
OnInstanceConstructed(ConsoleApplication7.Foo)

As you can see, the OnSuccess advice has been invoked twice because two constructors were chained. However, OnInstanceConstructed was invoked only once after the last constructor completed.

Let’s have a more complex example:

[MyAspect]
class Bar : Foo
{
    public Bar() 
    {
Console.WriteLine("Bar()"); } public Bar(int bar) : this() {
Console.WriteLine("Bar(int)"); } } static class Program { public static void Main() { Bar bar = new Bar(0); } }

The output is the following:

Foo()
OnSuccess(ConsoleApplication7.Foo)
Bar()
OnSuccess(ConsoleApplication7.Bar)
Bar(int)
OnSuccess(ConsoleApplication7.Bar)
OnInstanceConstructed(ConsoleApplication7.Foo) OnInstanceConstructed(ConsoleApplication7.Bar)

As expected, OnSuccess is invoked twice. It may be more surprising to see OnInstanceConstructed invoked twice. The reason is that the method is actually called only once, but once for each instance of the aspect, and here we have two instances of MyAspect: one on Foo, and the other on Bar.

If you want to have a single execution of the logic, you have to make it conditional to this.appliedOnType:

[OnInstanceConstructedAdvice]
public void OnInstanceConstructed()
{
    if (this.appliedTo == this.Instance.GetType())
    {
        Console.WriteLine("OnInstanceConstructed({0})", this.appliedTo);
    }
}

How does it work?

The OnInstanceConstructed advice needs to “know” that the constructor is the last in the chain.

Let’s look at the code after it has been transformed by PostSharp:

internal class Bar : Foo
{
    public Bar() : this(ConstructorDepth.Zero)
    {
    }

    protected Bar(ConstructorDepth __depth) : base(__depth.Increment())
    {
        Console.WriteLine("Bar");
        this.<>z__aspect2.OnSuccess(null);
        if (__depth.IsZero)
        {
            this.OnInstanceConstructed();
        }
    }

    public Bar(int bar) : this(bar, ConstructorDepth.Zero)
    {
    }

    protected Bar(int bar, ConstructorDepth __depth) : this(__depth.Increment())
    {
        Console.WriteLine("Bar(int)");
        this.<>z__aspect2.OnSuccess(null);
        if (__depth.IsZero)
        {
            this.OnInstanceConstructed();
        }
    }

    protected override void OnInstanceConstructed()
    {
        base.OnInstanceConstructed();
        this.<>z__aspect2.OnInstanceConstructed();
    }
}

As you can see, PostSharp does three things with every user-defined constructor:

  • Add an argument of type ConstructorDepth to the original constructor.
  • Create a new constructor with the original constructor signature, which invokes the original constructor (with the modified signature) with the parameter ConstructorDepth.Zero.
  • In the constructor chain, add the argument __depth.Increment() to the original invocation.

Therefore, we know when the current constructor depth is zero and we’re able to invoke OnInstanceConstructed. If you’re looking at your code with System.Reflection, you will see that there are twice as many constructors. Naturally, you should invoke only the public or internal constructors, which have the original signature and initialize the depth to zero. Except reflection, the modification is harmless to user code.

Summary

The OnInstanceConstructed advice is the only reliable way to execute logic in the aspect only once after the instance has been fully constructed. We needed this feature to implement Aggregatable, Undo/Redo and Threading Models in PostSharp 4.0, and made it in a way that can be useful to everybody.

Happy PostSharping!

Pingbacks and trackbacks (1)+

Comments are closed