Ordering aspects
When multiple aspect classes are defined, the order of their execution becomes critical.
Concepts
Per-project ordering
In Metalama, the execution order is static. This order is primarily the responsibility of the aspect library author, not the users of the aspect library.
Each aspect library should define the execution order of the aspects it introduces. This order should consider not only other aspects within the same library but also aspects defined in referenced aspect libraries.
When a project employs two unrelated aspect libraries or contains aspect classes, it must define the ordering within the project itself.
Order of application versus order of execution
Metalama adheres to the "matryoshka" model: your source code is the innermost doll, and aspects are added around it. The fully compiled code, inclusive of all aspects, resembles a fully assembled matryoshka. Executing a method is akin to disassembling the matryoshka: you commence with the outermost shell and progress to the original implementation.
It's crucial to remember that while Metalama constructs the matryoshka from the inside out, the code is executed from the outside in; in other words, the source code is executed last.
Therefore, the aspect application order and the aspect execution order are opposite.
Specifying the execution order
The execution order of aspects must be defined using the AspectOrderAttribute assembly-level custom attribute. The order of the aspect classes in the attribute corresponds to their execution order.
using Metalama.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 Metalama.Framework.Aspects;
[assembly: AspectOrder( typeof(Aspect1), typeof(Aspect2))]
[assembly: AspectOrder( typeof(Aspect2), typeof(Aspect3))]
This is akin to 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 an aspect library that defines a conflicting order, Metalama will emit a compilation error.
Note
Metalama will merge all [assembly: AspectOrder(...)]
attributes that it finds not only in the current project but also in all referenced projects or libraries. Therefore, you don't need to repeat the [assembly: AspectOrder(...)]
attributes in all projects that use aspects. It is sufficient to define them in projects that define aspects.
Example
The following code snippet demonstrates two aspects that add a method to the target type and display the list of methods defined on the target type before the aspect was applied. The execution order is defined as Aspect1 < Aspect2
. From this example, you can discern 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.
1using Doc.Ordering;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using System;
5using System.Linq;
6
7[assembly: AspectOrder( typeof(Aspect1), typeof(Aspect2) )]
8
9namespace Doc.Ordering
10{
11 internal class Aspect1 : TypeAspect
12 {
13 public override void BuildAspect( IAspectBuilder<INamedType> builder )
14 {
15 foreach ( var m in builder.Target.Methods )
16 {
17 builder.Advice.Override( m, nameof(this.Override) );
18 }
19 }
20
21 [Introduce]
22 public static void IntroducedMethod1()
23 {
24 Console.WriteLine( "Method introduced by Aspect1." );
25 }
26
27 [Template]
28 private dynamic? Override()
29 {
30 Console.WriteLine(
31 $"Executing Aspect1 on {meta.Target.Method.Name}. Methods present before applying Aspect1: "
32 + string.Join( ", ", meta.Target.Type.Methods.Select( m => m.Name ).ToArray() ) );
33
34 return meta.Proceed();
35 }
36 }
37
38 internal class Aspect2 : TypeAspect
39 {
40 public override void BuildAspect( IAspectBuilder<INamedType> builder )
41 {
42 foreach ( var m in builder.Target.Methods )
43 {
44 builder.Advice.Override( m, nameof(this.Override) );
45 }
46 }
47
48 [Introduce]
49 public static void IntroducedMethod2()
50 {
51 Console.WriteLine( "Method introduced by Aspect2." );
52 }
53
54 [Template]
55 private dynamic? Override()
56 {
57 Console.WriteLine(
58 $"Executing Aspect2 on {meta.Target.Method.Name}. Methods present before applying Aspect2: "
59 + string.Join( ", ", meta.Target.Type.Methods.Select( m => m.Name ).ToArray() ) );
60
61 return meta.Proceed();
62 }
63 }
64}
1using System;
2
3namespace Doc.Ordering
4{
5 [Aspect1]
6 [Aspect2]
7 internal class Foo
8 {
9 public static void SourceMethod()
10 {
11 Console.WriteLine( "Method defined in source code." );
12 }
13 }
14
15 public static class Program
16 {
17 public static void Main()
18 {
19 Console.WriteLine( "Executing SourceMethod:" );
20 Foo.SourceMethod();
21
22 Console.WriteLine( "---" );
23 Console.WriteLine( "Executing IntroducedMethod1:" );
Error CS0117: 'Foo' does not contain a definition for 'IntroducedMethod1'
24 Foo.IntroducedMethod1();
25
26 Console.WriteLine( "---" );
27 Console.WriteLine( "Executing IntroducedMethod2:" );
Error CS0117: 'Foo' does not contain a definition for 'IntroducedMethod2'
28 Foo.IntroducedMethod2();
29 }
30 }
31}
1using System;
2
3namespace Doc.Ordering
4{
5 [Aspect1]
6 [Aspect2]
7 internal class Foo
8 {
9 public static void SourceMethod()
10 {
11 Console.WriteLine("Executing Aspect1 on SourceMethod. Methods present before applying Aspect1: SourceMethod, IntroducedMethod2");
12 Console.WriteLine("Executing Aspect2 on SourceMethod. Methods present before applying Aspect2: SourceMethod");
13 Console.WriteLine("Method defined in source code.");
14 return;
15 }
16
17 public static void IntroducedMethod1()
18 {
19 Console.WriteLine("Method introduced by Aspect1.");
20 }
21
22 public static void IntroducedMethod2()
23 {
24 Console.WriteLine("Executing Aspect1 on IntroducedMethod2. Methods present before applying Aspect1: SourceMethod, IntroducedMethod2");
25 Console.WriteLine("Method introduced by Aspect2.");
26 return;
27 }
28 }
29
30 public static class Program
31 {
32 public static void Main()
33 {
34 Console.WriteLine("Executing SourceMethod:");
35 Foo.SourceMethod();
36
37 Console.WriteLine("---");
38 Console.WriteLine("Executing IntroducedMethod1:");
39 Foo.IntroducedMethod1();
40
41 Console.WriteLine("---");
42 Console.WriteLine("Executing IntroducedMethod2:");
43 Foo.IntroducedMethod2();
44 }
45 }
46}
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.
Several instances of the same aspect type on the same declaration
When multiple instances of the same aspect type are applied to the same declaration, one instance of the aspect, known as the primary instance, is selected and applied to the target. The other instances, known as secondary instances, are exposed on the IAspectInstance.SecondaryInstances property, which you can access from meta.AspectInstance or builder.AspectInstance. The aspect implementation is responsible for determining what to do with the secondary aspect instances.
The primary aspect instance is the instance that has been applied closest to the target declaration. The sorting criteria are as follows:
- Aspects defined using a custom attribute.
- Aspects added by another aspect (child aspects).
- Aspects inherited from another declaration.
- Aspects added by a fabric.
Within these individual categories, the ordering is currently undefined, meaning the build may be nondeterministic if the aspect implementation relies on that ordering.