Metalama 1.0 / / Metalama Documentation / Conceptual Documentation / Creating Aspects / Aspect Inheritance

Aspect Inheritance

Many aspects, such as INotifyPropertyChanged implementation or thread synchronization aspects, need to be inherited from the base class to which the aspect is applied, to all derived classes. That is, if a base class has a [NotifyPropertyChanged] aspect that adds calls to OnPropertyChanged to all property setters, it is logical that the aspect also affects the property setters of the derived classes.

This feature is called aspect inheritance. It is activated by adding the InheritedAttribute custom attribute to the aspect class. When an aspect is marked as inherited, its BuildAspect method is no longer only called for the direct target declaration of the aspect, but also for all derived declarations.

Aspect can be inherited along the following lines:

  • from a base class to derived classes;
  • from an interface to all classes implementing that interface;
  • from a virtual member to ts override members;
  • from an interface members to its implementations;
  • from a parameter of a virtual method to the corresponding parameter of all override methods;
  • from a parameter of an interface member to the corresponding parameter of all its implementations.

Example

The following type-level aspect is applied to a base class and is implicitly inherited by all derived classes.

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

namespace Doc.InheritedTypeLevel
{
    [Inherited]
    internal class InheritedAspectAttribute : TypeAspect
    {
        public override void BuildAspect( IAspectBuilder<INamedType> builder )
        {
            foreach ( var method in builder.Target.Methods )
            {
                builder.Advice.Override( method, nameof(this.MethodTemplate) );
            }
        }

        [Template]
        private dynamic? MethodTemplate()
        {
            Console.WriteLine( "Hacked!" );

            return meta.Proceed();
        }
    }
}
namespace Doc.InheritedTypeLevel
{
    [InheritedAspect]
    internal class BaseClass
    {
        public void Method1() { }

        public virtual void Method2() { }
    }

    internal class DerivedClass : BaseClass
    {
        public override void Method2()
        {
            base.Method2();
        }

        public void Method3() { }
    }

    internal class DerivedTwiceClass : DerivedClass
    {
        public override void Method2()
        {
            base.Method2();
        }

        public void Method4() { }
    }
}
using System;

namespace Doc.InheritedTypeLevel
{
    [InheritedAspect]
    internal class BaseClass
    {
        public void Method1()
        {
            Console.WriteLine("Hacked!");
            return;
        }

        public virtual void Method2()
        {
            Console.WriteLine("Hacked!");
            return;
        }
    }

    internal class DerivedClass : BaseClass
    {
        public override void Method2()
        {
            Console.WriteLine("Hacked!");
            base.Method2();
            return;
        }

        public void Method3()
        {
            Console.WriteLine("Hacked!");
            return;
        }
    }

    internal class DerivedTwiceClass : DerivedClass
    {
        public override void Method2()
        {
            Console.WriteLine("Hacked!");
            base.Method2();
            return;
        }

        public void Method4()
        {
            Console.WriteLine("Hacked!");
            return;
        }
    }
}

Cross-project inheritance

Aspect inheritance also works across project boundaries, that is, even when the base class is in a different project than the derived class.

To make this possible, the aspect instance on in the project containing the base class is serialized into a binary buffer, and stored as a managed resource in the assembly. When compiling the project containing the derived class, the aspect is deserialized from the binary buffer, and its BuildAspect method can be called.

Serialization is done using a custom formatter whose semantics are close to the legacy BinaryFormatter of the now obsolete [Serializable]. To mark a field or property as non-serializable, use the LamaNonSerializedAttribute custom attribute.

Eligibility of inherited aspects

The eligibility of an aspect is a set of rules that define on which target declarations an aspect can be legitimately applied. For details, see Defining the Eligibility of Aspects.

When an aspect is inherited, it has two sets of eligibility rules:

  • the normal eligibility rules define on which declarations the aspect can be expanded; typically this would not include any abstract members;
  • the inheritance eligibility rules define on which declarations the aspect can be added for inheritance; typically this would include abstract members.

When an inherited aspect is added to a target that match the inheritance eligibility rules but not the normal eligibility rules, an abstract aspect instance is added to that target. That is, BuildAspect method is not called for that target, but only for derived targets.

To define the eligibility rules that do not apply to the inheritance scenario, use the BuildEligibility method, use the ExceptForInheritance method, for instance:

Example

The following implementation of BuildEligibility specifies that the aspect will be applied abstractly when applied to an abstract method, that its BuildAspect method will not be invoke for the abstract method, but only for methods implementing the abstract method.s

            public override void BuildEligibility( IEligibilityBuilder<IMethod> builder )
{
    builder.ExceptForInheritance().MustBeNonAbstract();
}