Understanding the aspect framework design
Up to now, you have seen how to create simple aspects using the OverrideMethodAspect and OverrideFieldOrPropertyAspect. These classes can be considered API sugar designed to ease the creation of your first aspects. To go further, you must understand the design of the Metalama aspect framework.
Class diagram
An aspect is, by definition, a class that implements the IAspect<T> generic interface. The generic parameter of this interface is the type of declarations to which the aspect can be applied. For instance, an aspect that can be applied to a method must implement the IAspect<IMethod>
interface, and an aspect that can be applied to a named type must implement IAspect<INamedType>
.
The aspect author can use the BuildAspect method, inherited from the IAspect<T> interface, to build the aspect instance applied on a specific target declaration, thanks to an IAspectBuilder<TAspectTarget>.
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 details, see Transforming code.
2. Reporting and suppressing diagnostics
Aspects can report diagnostics (a single word for errors, warnings, and information messages) and suppress diagnostics reported by the C# compiler, analyzers, or other aspects.
For details about this feature, see Reporting and suppressing diagnostics.
3. Suggesting code fixes
Aspects can suggest code fixes to any diagnostic they report or suggest 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.
See Validating code from an aspect.
5. Defining its eligibility
Aspects can define which declarations they can be legally applied to.
See Defining the eligibility of aspects.
6. Adding other aspects to be applied
Aspects can add other aspects to the target code.
See 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 shows 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}
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}
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}