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. Before going 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) BuildEligibility(IEligibilityBuilder) } class IAspectBuilder { SkipAspect() TargetDeclaration } class IAdviser { Target With(declaration) } class ScopedDiagnosticSink { Report(...) Suppress(...) Suggest(...) } class AdviserExtensions { <<static>> Override(...) Introduce*(...) ImplementInterface(...) AddContract(...) AddInitializer(...) } class IAspectReceiver { Select(...) SelectMany(...) Where(...) AddAspect(...) AddAspectIfEligible(...) Validate(...) ValidateInboundReferences(...) ReportDiagnostic(...) SuppressDiagnostic(...) SuggestCodeFix(...) } IAspect --> IAspectBuilder : BuildAspect() receives IAspectBuilder --|> IAdviser : inherits IAspectBuilder --> ScopedDiagnosticSink : exposes IAspectBuilder --> IAspectReceiver : exposes AdviserExtensions --> IAdviser : provides extension\nmethods
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, suppressing diagnostics, and suggesting code fixes
Aspects can report diagnostics (a term encompassing errors, warnings, and information messages) and suppress diagnostics reported by the C# compiler, analyzers, or other aspects.
Aspects can suggest code fixes for any diagnostic they report or propose code refactorings.
For more information about this feature, refer to Reporting and suppressing diagnostics.
3. Performing advanced code validations
The builder.Outbound property allows registering validators for advanced scenarios:
- Validate the target declaration after it has been transformed by all aspects.
- Validate any references to the target declaration.
Refer to Validating code from an aspect.
4. Adding other aspects to be applied
The builder.Outbound property also allows adding other aspects to the target code.
Refer to Adding child aspects.
5. Defining its eligibility
Aspects can define which declarations they can be legally applied to.
Refer to Defining the eligibility of 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.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using System;
5
6namespace Doc.LogMethodAndProperty;
7
8[AttributeUsage( AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property )]
9public class LogAttribute : Aspect, IAspect<IMethod>, IAspect<IFieldOrProperty>
10{
11 public void BuildAspect( IAspectBuilder<IMethod> builder )
12 {
13 builder.Override( nameof(this.OverrideMethod) );
14 }
15
16 public void BuildAspect( IAspectBuilder<IFieldOrProperty> builder )
17 {
18 builder.Override( nameof(this.OverrideFieldOrProperty) );
19 }
20
21 [Template]
22 private dynamic? OverrideMethod()
23 {
24 Console.WriteLine( "Entering " + meta.Target.Method.ToDisplayString() );
25
26 try
27 {
28 return meta.Proceed();
29 }
30 finally
31 {
32 Console.WriteLine( " Leaving " + meta.Target.Method.ToDisplayString() );
33 }
34 }
35
36 [Template]
37 private dynamic? OverrideFieldOrProperty
38 {
39 get => meta.Proceed();
40
41 set
42 {
43 Console.WriteLine( "Assigning " + meta.Target.FieldOrProperty.ToDisplayString() );
44 meta.Proceed();
45 }
46 }
47}
1namespace Doc.LogMethodAndProperty;
2
3internal 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}
1using System;
2
3namespace Doc.LogMethodAndProperty;
4
5internal 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 _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 _field = value;
52 }
53 }
54}