MetalamaConceptual documentationCreating aspectsUnderstanding the framework design
Open sandboxFocusImprove this doc

Understanding the aspect framework design

Until now, you have learned how to create simple aspects using the OverrideMethodAspect and OverrideFieldOrPropertyAspect. These classes can be viewed as API sugar, designed to simplify the creation of your first aspects. To delve deeper, it is essential to understand the design of the Metalama aspect framework.

Class diagram

By definition, an aspect is a class that implements the IAspect<T> generic interface. The generic parameter of this interface represents the type of declarations to which the aspect can be applied. For instance, an aspect applicable to a method must implement the IAspect<IMethod> interface, while an aspect applicable to a named type must implement IAspect<INamedType>.

The aspect author can utilize the BuildAspect method, inherited from the IAspect<T> interface, to construct the aspect instance applied to a specific target declaration, using an IAspectBuilder<TAspectTarget>.

classDiagram

    class IAspect {
        BuildAspect(IAspectBuilder)
    }

    class IAspectBuilder {
        SkipAspect()
        TargetDeclaration
        AdviceFactory
    }

    class IAdviceFactory {
        Override*(...)
        Introduce*(...)
        AddInitializer*(...)
    }

    class ScopedDiagnosticSink {
        Report(...)
        Suppress(...)
        Suggest(...)
    }

    IAspect --> IAspectBuilder : BuildAspect() receives
    IAspectBuilder --> IAdviceFactory : exposes
    IAspectBuilder --> ScopedDiagnosticSink : exposes

Abilities of aspects

1. Transforming code

Aspects can perform the following transformations to code:

  • Apply a template to an existing method, i.e., add generated code to user-written code.
  • Introduce a newly generated member to an existing type.
  • Implement an interface into a type.

For more details, refer to Transforming code.

2. Reporting and suppressing diagnostics

Aspects can report diagnostics (a term encompassing errors, warnings, and information messages) and suppress diagnostics reported by the C# compiler, analyzers, or other aspects.

For more information about this feature, refer to Reporting and suppressing diagnostics.

3. Suggesting code fixes

Aspects can suggest code fixes for any diagnostic they report or propose code refactorings.

4. Validating the code that references the target declaration

Aspects can validate not only the target code but also any reference to the target declaration.

Refer to Validating code from an aspect.

5. Defining its eligibility

Aspects can define which declarations they can be legally applied to.

Refer to Defining the eligibility of aspects.

6. Adding other aspects to be applied

Aspects can add other aspects to the target code.

Refer to Adding child aspects.

7. Disabling itself

If an aspect instance decides it cannot be applied to its target, its implementation of the BuildAspect method can call the SkipAspect() method. The effect of this method is to prevent the aspect from providing any advice or child aspect and to set the IsSkipped to true.

The aspect may or may not report a diagnostic before calling SkipAspect(). Calling this method does not report any diagnostic.

8. Customizing its appearance in the IDE

By default, an aspect class is represented in the IDE by the class name trimmed of its Attribute suffix, if any. To override the default name, annotate the aspect class with the DisplayNameAttribute annotation.

Examples

Example: an aspect targeting methods, fields, and properties

The following example demonstrates an aspect that targets methods, fields, and properties with a single implementation class.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System;
4
5namespace Doc.LogMethodAndProperty
6{
7    [AttributeUsage( AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property )]
8    public class LogAttribute : Aspect, IAspect<IMethod>, IAspect<IFieldOrProperty>
9    {
10        public void BuildAspect( IAspectBuilder<IMethod> builder )
11        {
12            builder.Advice.Override( builder.Target, nameof(this.OverrideMethod) );
13        }
14
15        public void BuildAspect( IAspectBuilder<IFieldOrProperty> builder )
16        {
17            builder.Advice.Override( builder.Target, nameof(this.OverrideFieldOrProperty) );
18        }
19
20        [Template]
21        private dynamic? OverrideMethod()
22        {
23            Console.WriteLine( "Entering " + meta.Target.Method.ToDisplayString() );
24
25            try
26            {
27                return meta.Proceed();
28            }
29            finally
30            {
31                Console.WriteLine( " Leaving " + meta.Target.Method.ToDisplayString() );
32            }
33        }
34
35        [Template]
36        private dynamic? OverrideFieldOrProperty
37        {
38            get => meta.Proceed();
39
40            set
41            {
42                Console.WriteLine( "Assigning " + meta.Target.FieldOrProperty.ToDisplayString() );
43                meta.Proceed();
44            }
45        }
46    }
47}
Source Code
1namespace Doc.LogMethodAndProperty
2{


3    internal class Foo
4    {
5        [Log]
6        public int Method( int a, int b )
7        {
8            return a + b;
9        }



10
11        [Log]





12        public int Property { get; set; }


13
14        [Log]














15        public string? Field;
16    }
17}
Transformed Code
1using System;
2
3namespace Doc.LogMethodAndProperty
4{
5    internal class Foo
6    {
7        [Log]
8        public int Method(int a, int b)
9        {
10            Console.WriteLine("Entering Foo.Method(int, int)");
11            try
12            {
13                return a + b;
14            }
15            finally
16            {
17                Console.WriteLine(" Leaving Foo.Method(int, int)");
18            }
19        }
20
21        private int _property;
22
23        [Log]
24        public int Property
25        {
26            get
27            {
28                return _property;
29            }
30
31            set
32            {
33                Console.WriteLine("Assigning Foo.Property");
34                this._property = value;
35            }
36        }
37
38        private string? _field;
39
40        [Log]
41        public string? Field
42        {
43            get
44            {
45                return _field;
46            }
47
48            set
49            {
50                Console.WriteLine("Assigning Foo.Field");
51                this._field = value;
52            }
53        }
54    }
55}