When multiple instances of the same aspect type are applied to the same declaration, Metalama uses a primary/secondary instance model. Only one instance (the primary) is executed, but it has access to all other instances (the secondary instances) and can incorporate their configuration.
When does this happen?
Multiple instances of the same aspect type can be applied to a declaration through various sources:
- Custom attribute: An aspect applied directly via an attribute like
[MyAspect]. Multiple custom attributes appear as different aspect instances. - Fabrics: An aspect added programmatically by a fabric.
- Child aspects: An aspect added by another aspect using the Outbound property.
- Required aspects: An aspect added by another aspect using the
builder.RequireAspectmethod. - Inherited aspects: An aspect inherited from a base class or interface when marked with InheritableAttribute.
When the same aspect type is applied from multiple sources, or multiple times from the same source, Metalama must determine which instance to execute.
Primary instance selection
The primary instance is selected based on proximity to the target declaration. The selection criteria, from highest to lowest priority, are:
- Aspects defined using a custom attribute
- Aspects added by another aspect (child aspects)
- Aspects required by another aspect
- Aspects inherited from another declaration
- Aspects added by a fabric
Within each category, the ordering is currently undefined, which can lead to non-deterministic behavior if your aspect relies on that ordering.
Accessing secondary instances
Secondary instances are accessible through the SecondaryInstances property. You can access this property from:
builder.AspectInstance.SecondaryInstancesin theBuildAspectmethodmeta.AspectInstance.SecondaryInstancesin template methods
Each secondary instance provides access to:
- The aspect instance itself (via the
Aspectproperty), which you can cast to your aspect type to read its properties. - Information about what added this instance, such as an attribute, fabric, or parent aspect (via the
Predecessorsproperty).
Common patterns
When your aspect encounters secondary instances, you can handle them in several ways. The most common patterns are merging configuration from all instances, or reporting a warning when duplicates are detected.
Merging configuration
A common approach is to merge all aspect instances into a single one. The merging logic is specific to each aspect.
The following example demonstrates a logging aspect that merges categories from all instances (primary and secondary) when multiple instances are applied to the same method.
1using System;
2
3namespace Doc.MultipleInstances;
4
5public class OrderService
6{
7 // This method has [Log] from both the custom attribute (Category="Orders")
8 // and from the fabric (Category="Monitoring"). The aspect merges both categories.
9 [Log( Category = "Orders" )]
10 public void PlaceOrder( string productId, int quantity )
11 {
12 Console.WriteLine( "Business logic here." );
13 }
14
15 // This method only has [Log] from the fabric (default Category="Monitoring").
16 public void GetOrderStatus( string orderId )
17 {
18 Console.WriteLine( "Business logic here." );
19 }
20}
21
1using System;
2
3namespace Doc.MultipleInstances;
4
5public class OrderService
6{
7 // This method has [Log] from both the custom attribute (Category="Orders")
8 // and from the fabric (Category="Monitoring"). The aspect merges both categories.
9 [Log(Category = "Orders")]
10 public void PlaceOrder(string productId, int quantity)
11 {
12 Console.WriteLine("[Monitoring, Orders] Entering OrderService.PlaceOrder");
13 try
14 {
15 Console.WriteLine("Business logic here.");
16 return;
17 }
18 finally
19 {
20 Console.WriteLine("[Monitoring, Orders] Leaving OrderService.PlaceOrder");
21 }
22 }
23
24 // This method only has [Log] from the fabric (default Category="Monitoring").
25 public void GetOrderStatus(string orderId)
26 {
27 Console.WriteLine("[Monitoring] Entering OrderService.GetOrderStatus");
28 try
29 {
30 Console.WriteLine("Business logic here.");
31 return;
32 }
33 finally
34 {
35 Console.WriteLine("[Monitoring] Leaving OrderService.GetOrderStatus");
36 }
37 }
38}
39
In this example, the PlaceOrder method has two [Log] instances: one from the custom attribute with Category="Orders", and one from the fabric with Category="Monitoring". The aspect merges both categories, resulting in [Monitoring, Orders] in the log output. The GetOrderStatus method has only one instance from the fabric, showing just [Monitoring].
Reporting warnings on duplicates
You can warn users when they've applied the same aspect multiple times unintentionally. In this example, a warning is reported when GetOrderStatus has [Log] from both the custom attribute and the fabric.
1using System;
2
3namespace Doc.SecondaryInstancesWarning;
4
5public class OrderService
6{
7 // This method only has [Log] from the fabric - no warning.
8 public void PlaceOrder( string productId, int quantity )
9 {
10 Console.WriteLine( "Placing order." );
11 }
12
13 // This method has [Log] from both the attribute and the fabric.
14 // A warning will be reported about the duplicate.
15 [Log]
Warning MY001: The [Log] aspect is applied 2 times to the same method. Only the primary instance will be used.
16 public void GetOrderStatus( string orderId )
17 {
18 Console.WriteLine( "Getting order status." );
19 }
20}
21
1using System;
2
3namespace Doc.SecondaryInstancesWarning;
4
5public class OrderService
6{
7 // This method only has [Log] from the fabric - no warning.
8 public void PlaceOrder(string productId, int quantity)
9 {
10 Console.WriteLine("Entering OrderService.PlaceOrder");
11 try
12 {
13 Console.WriteLine("Placing order.");
14 return;
15 }
16 finally
17 {
Warning MY001: The [Log] aspect is applied 2 times to the same method. Only the primary instance will be used.
18 Console.WriteLine("Leaving OrderService.PlaceOrder");
19 }
20 }
21
22 // This method has [Log] from both the attribute and the fabric.
23 // A warning will be reported about the duplicate.
24 [Log]
25 public void GetOrderStatus(string orderId)
26 {
27 Console.WriteLine("Entering OrderService.GetOrderStatus");
28 try
29 {
30 Console.WriteLine("Getting order status.");
31 return;
32 }
33 finally
34 {
35 Console.WriteLine("Leaving OrderService.GetOrderStatus");
36 }
37 }
38}
39
Relationship to predecessors
Don't confuse SecondaryInstances with Predecessors:
SecondaryInstancesare other instances of the same aspect type on the same target. These are "siblings."Predecessorsare the artifacts (attributes, fabrics, parent aspects) that created this aspect instance. These are "parents."
For example, if [ParentAspect] adds a child [ChildAspect] to a method, and you also apply [ChildAspect] via a custom attribute:
- The custom attribute instance is the primary (higher priority)
- The child aspect instance is a secondary instance
- The predecessor of the child aspect instance is
[ParentAspect]
Excluding aspects from specific targets
In some cases, you may want to prevent an aspect from being applied entirely, rather than handling multiple instances.
To completely prevent an aspect from being applied to a specific declaration (regardless of fabrics or other sources), use the built-in ExcludeAspectAttribute attribute:
// Prevents any [Log] aspect from being applied to this method
[ExcludeAspect(typeof(LogAttribute))]
public void GetOrderStatus(string orderId)
{
// This method will not be logged, even if a fabric adds [Log]
}