Many aspects, such as the INotifyPropertyChanged
implementation or thread synchronization aspects, must be inherited from the base class to which the aspect is applied, extending to all derived classes. This means that if a base class has a [NotifyPropertyChanged]
aspect that adds calls to OnPropertyChanged
to all property setters, it is logical for the aspect to also affect the property setters of the derived classes.
This feature is referred to as aspect inheritance. It is activated by adding the [Inheritable] custom attribute to the aspect class. When an aspect is marked as inheritable, its BuildAspect method is invoked not only for the direct target declaration of the aspect but also for all derived declarations.
An aspect can be inherited in the following scenarios:
- From a base class to derived classes;
- From a base interface to derived interfaces;
- From an interface to all types implementing that interface;
- From a
virtual
orabstract
member to itsoverride
members; - From an interface member to its implementations;
- From a parameter of a
virtual
orabstract
method to the corresponding parameter of alloverride
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.
1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using System;
5
6namespace Doc.InheritedTypeLevel;
7
8[Inheritable]
9internal class InheritedAspectAttribute : TypeAspect
10{
11 public override void BuildAspect( IAspectBuilder<INamedType> builder )
12 {
13 foreach ( var method in builder.Target.Methods )
14 {
15 builder.With( method ).Override( nameof(this.MethodTemplate) );
16 }
17 }
18
19 [Template]
20 private dynamic? MethodTemplate()
21 {
22 Console.WriteLine( "Hacked!" );
23
24 return meta.Proceed();
25 }
26}
1namespace Doc.InheritedTypeLevel;
2
3[InheritedAspect]
4internal class BaseClass
5{
6 public void Method1() { }
7
8 public virtual void Method2() { }
9}
10
11internal class DerivedClass : BaseClass
12{
13 public override void Method2()
14 {
15 base.Method2();
16 }
17
18 public void Method3() { }
19}
20
21internal class DerivedTwiceClass : DerivedClass
22{
23 public override void Method2()
24 {
25 base.Method2();
26 }
27
28 public void Method4() { }
29}
1using System;
2
3namespace Doc.InheritedTypeLevel;
4
5[InheritedAspect]
6internal class BaseClass
7{
8 public void Method1()
9 {
10 Console.WriteLine("Hacked!");
11 }
12
13 public virtual void Method2()
14 {
15 Console.WriteLine("Hacked!");
16 }
17}
18
19internal class DerivedClass : BaseClass
20{
21 public override void Method2()
22 {
23 Console.WriteLine("Hacked!");
24 base.Method2();
25 }
26
27 public void Method3()
28 {
29 Console.WriteLine("Hacked!");
30 }
31}
32
33internal class DerivedTwiceClass : DerivedClass
34{
35 public override void Method2()
36 {
37 Console.WriteLine("Hacked!");
38 base.Method2();
39 }
40
41 public void Method4()
42 {
43 Console.WriteLine("Hacked!");
44 }
45}
Conditional inheritance
The [Inheritable] custom attribute causes all instances of the aspect class to be inheritable, irrespective of their fields or properties. If you wish to base the inheritance decision on fields or properties of the aspect, your aspect must implement the IConditionallyInheritableAspect.
Note that when the IConditionallyInheritableAspect interface is implemented, the refactoring menu will always suggest adding the aspect to a declaration, even if the aspect is eligible for inheritance only on the target declaration.
Cross-project inheritance
Aspect inheritance also operates across project boundaries, even when the base class is in a different project than the derived class.
To facilitate this, the aspect instance 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 invoked.
Serialization uses a custom formatter whose semantics closely resemble the legacy BinaryFormatter of the now obsolete [Serializable]
. To mark a field or property as non-serializable, use the NonCompileTimeSerializedAttribute custom attribute. For details, see Serialization of aspects and other compile-time classes.
Eligibility of inherited aspects
The eligibility of an aspect is a set of rules defining which target declarations an aspect can be legitimately applied to. 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 which declarations the aspect can be added to for inheritance; typically, this would include abstract members.
When an inherited aspect is added to a target that matches the inheritance eligibility rules but not the normal eligibility rules, an abstract aspect instance is added to that target. That is, the BuildAspect method is not invoked 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 and the ExceptForInheritance method.
Example
The following implementation of BuildEligibility specifies that the aspect will be applied abstractly when applied to an abstract method. Its BuildAspect method will not be invoked for the abstract method but only for methods implementing the abstract method.
public override void BuildEligibility( IEligibilityBuilder<IMethod> builder )
{
builder.ExceptForInheritance().MustNotBeAbstract();
}