Project "Caravela" 0.3 / / Caravela Documentation / Conceptual Documentation / Creating Aspects / Ordering Aspects

Ordering Aspects

When there are several aspect classes in the project, their order of execution is significant.

Concepts

Per-project ordering

In Caravela, the order of execution is static. It is principally a concern of the aspect library author, not one of the user of the aspect library.

Each aspect library should define the order of execution of the aspect it defines, not only with regards to other aspects of the same library, but also to the aspects defined in the referenced aspect libraries.

When a project uses two unrelated aspect libraries, or when a project defines its own aspects, it has to define the ordering in the project itself.

Order of application versus order of execution

Caravela follows what we call the "matryoshka" model: your source code is the innermost doll, and aspects are added around it. The fully compiled code, with all aspects, is like the fully assembled matryoshka. Executing a method is like disassembling the matryoshka: you start with the outermost shell, and you continue to the original implementation.

It is important to remember that Caravela builds the matryoshka from the inside to the outside, but the code is executed is from the outside to the inside, i.e. the source code is executed last.

Therefore, the aspect application order and the aspect execution order are opposite.

Specifying the execution order

Aspects must be ordered using the AspectOrderAttribute assembly-level custom attribute. The order in which the aspect classes in the constructor correspond to their order of execution.

            using Caravela.Framework.Aspects;
[assembly: AspectOrder( typeof(Aspect1), typeof(Aspect2), typeof(Aspect3))]

          

You can specify partial order relationships. The aspect framework will merge all partial relationships and determine the global order for the current project.

For instance, the following code snippet is equivalent to the previous one:

            using Caravela.Framework.Aspects;
[assembly: AspectOrder( typeof(Aspect1), typeof(Aspect2))]
[assembly: AspectOrder( typeof(Aspect2), typeof(Aspect3))]

          

This is like in mathematics: if we have a < b and b < c, then we have a < c and the ordered sequence is {a, b, c}.

If you specify conflicting relationships, or import aspect library that define conflicting ordering, Caravela will emit a compilation error.

Example

The following code snippet shows two aspects that both add a method to the target type and display the list of methods that were defined on the target type before the aspect was applied. The order of execution is defined as Aspect1 < Aspect2. You can see from this example that the order of application of aspects is opposite. Aspect2 is applied first and sees the source code, then Aspect1 is applied and sees the method added by Aspect1. The modified method body of SourceMethod shows that the aspects are executed in this order: Aspect1, Aspect2, then the original method.

                using System;
using System.Linq;
using Caravela.Framework.Aspects;
using Caravela.Framework.Code;

using Caravela.Documentation.SampleCode.AspectFramework.Ordering;
[assembly: AspectOrder(typeof(Aspect1), typeof(Aspect2))]


namespace Caravela.Documentation.SampleCode.AspectFramework.Ordering
{
    internal class Aspect1 : Attribute, IAspect<INamedType>
    {
        public void BuildAspect(IAspectBuilder<INamedType> builder)
        {
            foreach (var m in builder.Target.Methods)
            {
                builder.AdviceFactory.OverrideMethod(m, nameof(Override));
            }
        }

        [Introduce]
        public static void IntroducedMethod1()
        {
            Console.WriteLine("Method introduced by Aspect1.");
        }

        [Template]
        private dynamic? Override()
        {
            Console.WriteLine($"Executing Aspect1 on {meta.Target.Method.Name}. Methods present before applying Aspect1: "
                + string.Join(", ", meta.Target.Type.Methods.Select(m => m.Name).ToArray()));

            return meta.Proceed();
        }
    }

    internal class Aspect2 : Attribute, IAspect<INamedType>
    {
        public void BuildAspect(IAspectBuilder<INamedType> builder)
        {
            foreach (var m in builder.Target.Methods)
            {
                builder.AdviceFactory.OverrideMethod(m, nameof(Override));
            }
        }


        [Introduce]
        public static void IntroducedMethod2()
        {
            Console.WriteLine("Method introduced by Aspect2.");
        }

        [Template]
        private dynamic? Override()
        {
            Console.WriteLine($"Executing Aspect2 on {meta.Target.Method.Name}. Methods present before applying Aspect2: "
                + string.Join(", ", meta.Target.Type.Methods.Select(m => m.Name).ToArray()));

            return meta.Proceed();
        }

    }
}

              
                using System;
using Caravela.Framework.Aspects;


namespace Caravela.Documentation.SampleCode.AspectFramework.Ordering
{
    [Aspect1, Aspect2]
    internal class TargetCode
    {
        public static void SourceMethod()
        {
            Console.WriteLine("Method defined in source code.");
        }


    }

    public static class Program
    {

        public static void Main()
        {
            Console.WriteLine("Executing SourceMethod:");
            TargetCode.SourceMethod();

            Console.WriteLine("---");
            Console.WriteLine("Executing IntroducedMethod1:");
             TargetCode.IntroducedMethod1();

            Console.WriteLine("---");
            Console.WriteLine("Executing IntroducedMethod2:");
             TargetCode.IntroducedMethod2();
        }

    }
}
              
                using System;
using Caravela.Framework.Aspects;


namespace Caravela.Documentation.SampleCode.AspectFramework.Ordering
{
    [Aspect1, Aspect2]
    internal class TargetCode
    {
        public static void SourceMethod()
        {
            Console.WriteLine("Executing Aspect1 on SourceMethod. Methods present before applying Aspect1: SourceMethod, IntroducedMethod2");
            Console.WriteLine("Executing Aspect2 on SourceMethod. Methods present before applying Aspect2: SourceMethod");
            Console.WriteLine("Method defined in source code.");
            goto __aspect_return_1;
        __aspect_return_1:
            return;
        }


        public static void IntroducedMethod2()
        {
            Console.WriteLine("Executing Aspect1 on IntroducedMethod2. Methods present before applying Aspect1: SourceMethod, IntroducedMethod2");
            Console.WriteLine("Method introduced by Aspect2.");
            return;
        }

        public static void IntroducedMethod1()
        {
            Console.WriteLine("Method introduced by Aspect1.");
        }

    }

    public static class Program
    {

        public static void Main()
        {
            Console.WriteLine("Executing SourceMethod:");
            TargetCode.SourceMethod();

            Console.WriteLine("---");
            Console.WriteLine("Executing IntroducedMethod1:");
            TargetCode.IntroducedMethod1();

            Console.WriteLine("---");
            Console.WriteLine("Executing IntroducedMethod2:");
            TargetCode.IntroducedMethod2();
        }

    }
}
              
Executing SourceMethod:
Executing Aspect1 on SourceMethod. Methods present before applying Aspect1: SourceMethod, IntroducedMethod2
Executing Aspect2 on SourceMethod. Methods present before applying Aspect2: SourceMethod
Method defined in source code.
---
Executing IntroducedMethod1:
Method introduced by Aspect1.
---
Executing IntroducedMethod2:
Executing Aspect1 on IntroducedMethod2. Methods present before applying Aspect1: SourceMethod, IntroducedMethod2
Method introduced by Aspect2.