Open sandboxFocusImprove this doc

LAMA0551: The base type's OnConstructed method requires a constructor accepting InitializationContext

Severity Error
Reported by Metalama.Framework
Message The aspect '<AspectType>' targets type '<TargetType>' whose base type '<BaseType>' defines an 'OnConstructed(InitializationContext)' method but has no instance constructor accepting an 'InitializationContext' parameter. The base type must provide such a constructor (and call 'OnConstructed' from it, guarded by 'IsHandled(InitializationSlot.OnConstructed)') so that derived types can pass 'context.Descend(InitializationSlot.OnConstructed)' and skip the base's OnConstructed call.

Cause

When an aspect calls AddInitializer with AfterLastInstanceConstructor, Metalama adds the aspect's code to an OnConstructed( InitializationContext ) method and calls this method at the end of every constructor of the target type. If a base type already defines a virtual OnConstructed( InitializationContext ) method, Metalama overrides it in the target type and calls base.OnConstructed(...) from the override.

The base constructor must then skip its own call to OnConstructed, otherwise the method would run twice: once from the base constructor and once from the derived constructor. To make this possible, the derived constructor passes an InitializationContext to the base constructor, and the base constructor calls OnConstructed only if the context doesn't mark it as handled.

Metalama reports LAMA0551 when the type <BaseType> that declares the virtual OnConstructed( InitializationContext ) method has no constructor with an InitializationContext parameter. This typically happens when you write the base type by hand, without an aspect.

In the following example, the Entity base class defines OnConstructed, but its constructor has no InitializationContext parameter.

1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using System;
5
6namespace Doc.LAMA0551.Error;
7
8public class LogConstructedAttribute : TypeAspect
9{
10    public override void BuildAspect( IAspectBuilder<INamedType> builder )
11    {
12        // Adds the template to the OnConstructed method, which runs after the last constructor.
13        builder.AddInitializer( nameof(this.LogConstruction), InitializerKind.AfterLastInstanceConstructor );
14    }
15
16    [Template]
17    private void LogConstruction()
18    {
19        Console.WriteLine( $"{meta.Target.Type.Name} constructed." );
20    }
21}
22
1using Metalama.Framework.RunTime.Initialization;
2
3namespace Doc.LAMA0551.Error;
4
5public class Entity
6{
7    // Error: Entity defines OnConstructed but no constructor accepting an InitializationContext.
8    public Entity() { }
9
10    public virtual void OnConstructed( InitializationContext context = default ) { }
11}
12
13[LogConstructed]
Error LAMA0551: The aspect 'LogConstructed' targets type 'Order' whose base type 'Entity' defines an 'OnConstructed(InitializationContext)' method but has no instance constructor accepting an 'InitializationContext' parameter. The base type must provide such a constructor (and call 'OnConstructed' from it, guarded by 'IsHandled(InitializationSlot.OnConstructed)') so that derived types can pass 'context.Descend(InitializationSlot.OnConstructed)' and skip the base's OnConstructed call.

14public partial class Order : Entity
15{
16    public Order() { }
17}
18

Resolution

Add an InitializationContext parameter to the constructor of the base type, with a default value, and call OnConstructed from this constructor only when context.IsHandled( InitializationSlot.OnConstructed ) returns false. Metalama then makes the constructors of the derived type pass a context that marks OnConstructed as handled.

In the following example, the constructor of Entity accepts an InitializationContext.

1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using System;
5
6namespace Doc.LAMA0551.Fixed;
7
8public class LogConstructedAttribute : TypeAspect
9{
10    public override void BuildAspect( IAspectBuilder<INamedType> builder )
11    {
12        // Adds the template to the OnConstructed method, which runs after the last constructor.
13        builder.AddInitializer( nameof(this.LogConstruction), InitializerKind.AfterLastInstanceConstructor );
14    }
15
16    [Template]
17    private void LogConstruction()
18    {
19        Console.WriteLine( $"{meta.Target.Type.Name} constructed." );
20    }
21}
22
Source Code
1using Metalama.Framework.RunTime.Initialization;
2


3namespace Doc.LAMA0551.Fixed;
4
5public class Entity
6{
7    // Fixed: the constructor accepts an InitializationContext and calls OnConstructed
8    // only if a derived type doesn't handle it.
9    public Entity( InitializationContext context = default )
10    {
11        if ( !context.IsHandled( InitializationSlot.OnConstructed ) )
12        {
13            this.OnConstructed( context );
14        }
15    }
16
17    public virtual void OnConstructed( InitializationContext context = default ) { }
18}
19
20[LogConstructed]
21public partial class Order : Entity
22{
23    public Order() { }
24}




25
Transformed Code
1using System;
2using Metalama.Framework.RunTime;
3using Metalama.Framework.RunTime.Initialization;
4
5namespace Doc.LAMA0551.Fixed;
6
7public class Entity
8{
9    // Fixed: the constructor accepts an InitializationContext and calls OnConstructed
10    // only if a derived type doesn't handle it.
11    public Entity(InitializationContext context = default)
12    {
13        if (!context.IsHandled(InitializationSlot.OnConstructed))
14        {
15            this.OnConstructed(context);
16        }
17    }
18
19    public virtual void OnConstructed(InitializationContext context = default) { }
20}
21
22[LogConstructed]
23public partial class Order : Entity
24{
25    public Order([AspectGenerated] InitializationContext context = default) : base(context.Descend(InitializationSlot.OnConstructed))
26    {
27        if (!context.IsHandled(InitializationSlot.OnConstructed))
28        {
29            this.OnConstructed(context);
30        }
31    }
32
33    public override void OnConstructed(InitializationContext context = default)
34    {
35        base.OnConstructed(context);
36        Console.WriteLine("Order constructed.");
37    }
38}
39

See also