Metalama 1.0 / / Metalama Documentation / Conceptual Documentation / Fundamental Concepts / Abilities of Aspects

Abilities of Aspects

An aspect is, by definition, a class that implements the IAspect<T> generic interface. The generic parameter of this interface is the type of declaration to which that 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>.

Aspects have different abilities which are expounded below. The aspect author can use or configure these abilities in the following method inherited from the IAspect<T> interface:

classDiagram class IAspect { BuildAspect(IAspectBuilder) } class IAspectBuilder { SkipAspect() TargetDeclaration AdviceFactory } class IAdviceFactory { Override*(...) Introduce*(...) AddInitializer*(...) } class IDiagnosticSink { Report(...) Suppress(...) Suggest(...) } IAspect --> IAspectBuilder : BuildAspect() receives IAspectBuilder --> IAdviceFactory : exposes IAspectBuilder --> IDiagnosticSink : exposes

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 new generated member to an existing type.
  • Implement an interface into a type.

For details, see Transforming Code

Reporting and suppressing diagnostics

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

For details about this feature, see Reporting and Suppressing Diagnostics.

Suggest code fixes

Aspects can suggest code fixes to any diagnostic it reports, or suggest code refactorings.

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.

Eligibility

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

See Defining the Eligibility of Aspects.

Adding other aspects to be applied

Aspects can add other aspects to the target code.

See Adding Child Aspects.

Disabling itself

If an aspect instance decides that it cannot be applied to the target to which it has been applied, 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.

Customize its appearance in the IDE.

By default, an aspect class is represented in the IDE by the name of this class without the 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.

using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using System;

namespace Doc.LogMethodAndProperty
{
    [AttributeUsage( AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property )]
    public class LogAttribute : Aspect, IAspect<IMethod>, IAspect<IFieldOrProperty>
    {
        public void BuildAspect( IAspectBuilder<IMethod> builder )
        {
            builder.Advice.Override( builder.Target, nameof(this.OverrideMethod) );
        }

        public void BuildAspect( IAspectBuilder<IFieldOrProperty> builder )
        {
            builder.Advice.Override( builder.Target, nameof(this.OverrideFieldOrProperty) );
        }

        [Template]
        private dynamic? OverrideMethod()
        {
            Console.WriteLine( "Entering " + meta.Target.Method.ToDisplayString() );

            try
            {
                return meta.Proceed();
            }
            finally
            {
                Console.WriteLine( " Leaving " + meta.Target.Method.ToDisplayString() );
            }
        }

        [Template]
        private dynamic? OverrideFieldOrProperty
        {
            get => meta.Proceed();

            set
            {
                Console.WriteLine( "Assigning " + meta.Target.FieldOrProperty.ToDisplayString() );
                meta.Proceed();
            }
        }
    }
}
namespace Doc.LogMethodAndProperty
{
    internal class Foo
    {
        [Log]
        public int Method( int a, int b )
        {
            return a + b;
        }

        [Log]
        public int Property { get; set; }

        [Log]
        public string? Field;
    }
}
using System;

namespace Doc.LogMethodAndProperty
{
    internal class Foo
    {
        [Log]
        public int Method(int a, int b)
        {
            Console.WriteLine("Entering Foo.Method(int, int)");
            try
            {
                return a + b;
            }
            finally
            {
                Console.WriteLine(" Leaving Foo.Method(int, int)");
            }
        }


        private int _property;

        [Log]
        public int Property
        {
            get
            {
                return _property;
            }

            set
            {
                Console.WriteLine("Assigning Foo.Property");
                this._property = value;
            }
        }


        private string? _field;

        [Log]
        public string? Field
        {
            get
            {
                return _field;
            }

            set
            {
                Console.WriteLine("Assigning Foo.Field");
                this._field = value;
            }
        }
    }
}

Code model versioning