When you define multiple aspect classes, execution order becomes critical.
Concepts
Per-project ordering
Note
Defining the execution order is the primary responsibility of the aspect library author, not the users of the aspect library. Aspect libraries that know about each other should set their execution order properly to avoid user confusion.
Each aspect library should define the execution order of the aspects it introduces. This order should consider not only 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, including all aspects, resembles a fully assembled matryoshka. Executing a method is like disassembling the matryoshka: you start with the outermost shell and progress to the original implementation.

Remember that while Metalama, at build time, constructs the matryoshka from the inside out, at run time, the code is executed from the outside in—in other words, the source code is executed last.
Therefore, the build-time order of applying aspects and the run-time order of executing aspects are usually opposite.
Specifying the execution order
By default, aspects execute in alphabetical order at run time. This order isn't intended to be correct, but at least it's deterministic.
Define the execution order using the AspectOrderAttribute assembly-level custom attribute. The order of the aspect classes in the attribute corresponds to their execution order. To avoid ambiguities, you must explicitly supply the AspectOrderDirection value (RunTime or CompileTime) for which you're specifying the aspect order.
The following two snippets are equivalent:
using Metalama.Framework.Aspects;
[assembly: AspectOrder( AspectOrderDirection.RunTime, typeof(Aspect1), typeof(Aspect2), typeof(Aspect3))]
using Metalama.Framework.Aspects;
[assembly: AspectOrder( AspectOrderDirection.CompileTime, typeof(Aspect3), typeof(Aspect2), typeof(Aspect1))]
These custom attributes define the run-time execution order:
flowchart LR Aspect1 --> Aspect2 Aspect2 --> Aspect3
Partial relationships
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 ones:
using Metalama.Framework.Aspects;
[assembly: AspectOrder( AspectOrderDirection.RunTime, typeof(Aspect1), typeof(Aspect2))]
[assembly: AspectOrder( AspectOrderDirection.RunTime, typeof(Aspect2), typeof(Aspect3))]
These two attributes define the following relationships:
This is like 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 emits a compilation error.
Note
Metalama merges 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's sufficient to define them in projects that define aspects.
Inherited aspects
By default, relationships specified with AspectOrderAttribute also apply to derived aspect classes.
For instance, consider the following aspects:
abstract class ExceptionHandlingAspect;
class RetryAspect : ExceptionHandlingAspect;
class WrapExceptionAspect : ExceptionHandlingAspect;
abstract class CacheAspect;
class B1 : MemoryCacheAspect;
class B2 : RedisCacheAspect;
Consider the following order attributes, ordering only abstract aspects:
using Metalama.Framework.Aspects;
[assembly: AspectOrder( AspectOrderDirection.RunTime, typeof(CacheAspect), typeof(ExceptionHandlingAspect))]
We don't explicitly order concrete aspect classes, so alphabetical ordering automatically applies.
The resulting run-time aspect order is as follows:
flowchart LR MemoryCacheAspect --> RedisCacheAspect --> RetryAspect --> WrapExceptionAspect
To disable this behavior, set the ApplyToDerivedTypes property to false.
How does it work?
Under the hood, Metalama performs a topological sort on a graph composed of all relationships found in the current project and all its dependencies.
When a pair of aspects don't have any specific ordering relationship from any source, Metalama falls back to alphabetical ordering to avoid non-determinism.
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 see 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 Aspect2. 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( AspectOrderDirection.RunTime, typeof( Aspect1 ), typeof( Aspect2 ) )]
8
9namespace Doc.Ordering;
10
11internal class Aspect1 : TypeAspect
12{
13 public override void BuildAspect( IAspectBuilder<INamedType> builder )
14 {
15 foreach ( var m in builder.Target.Methods )
16 {
17 builder.With( m ).Override( 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
38internal class Aspect2 : TypeAspect
39{
40 public override void BuildAspect( IAspectBuilder<INamedType> builder )
41 {
42 foreach ( var m in builder.Target.Methods )
43 {
44 builder.With( m ).Override( 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}
1using System;
2
3namespace Doc.Ordering;
4
5[Aspect1]
6[Aspect2]
7internal partial class Foo
8{
9 public static void SourceMethod()
10 {
11 Console.WriteLine( "Method defined in source code." );
12 }
13}
14
15public 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:" );
24 Foo.IntroducedMethod1();
25
26 Console.WriteLine( "---" );
27 Console.WriteLine( "Executing IntroducedMethod2:" );
28 Foo.IntroducedMethod2();
29 }
30}
1using System;
2
3namespace Doc.Ordering;
4
5[Aspect1]
6[Aspect2]
7internal partial 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 }
15
16 public static void IntroducedMethod1()
17 {
18 Console.WriteLine("Method introduced by Aspect1.");
19 }
20
21 public static void IntroducedMethod2()
22 {
23 Console.WriteLine("Executing Aspect1 on IntroducedMethod2. Methods present before applying Aspect1: SourceMethod, IntroducedMethod2");
24 Console.WriteLine("Method introduced by Aspect2.");
25 }
26}
27
28public static class Program
29{
30 public static void Main()
31 {
32 Console.WriteLine("Executing SourceMethod:");
33 Foo.SourceMethod();
34
35 Console.WriteLine("---");
36 Console.WriteLine("Executing IntroducedMethod1:");
37 Foo.IntroducedMethod1();
38
39 Console.WriteLine("---");
40 Console.WriteLine("Executing IntroducedMethod2:");
41 Foo.IntroducedMethod2();
42 }
43}
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.