MetalamaConceptual documentationCreating aspectsAdvising codeOverriding constructors
Open sandboxFocusImprove this doc

Overriding constructors

Overriding constructors is an advanced technique with only a few limited use cases. Before opting for this approach, we recommend considering other alternatives.

When to override constructors

We recommend overriding constructors only if none of the following approaches work for your case.

Approach Scenario
Adding initializers You can add an initializer to an object or type constructor when you want to call an initialization statement before any source code, typically to initialize the type or object. The drawback of this approach is that it does not support T# templates.
Introducing constructor parameters When you want to introduce a parameter, typically a dependency, and pull it from constructors of derived types.

How to override a constructor

Overriding a constructor works in a similar way to overriding a method.

Your aspect must implement the BuildAspect method and invoke the builder.Advice.Override method.

To invoke the original implementation, call meta.Proceed. Note that, unlike methods, you can meta.Proceed only once when overriding a constructor.

Warning

Any code generated by the template is always applied after the call to the next constructor using the : base(...) or : this(...) construct.

Example: logging

The following example illustrates how overriding constructors works. The LogConstructors aspect overrides all constructors of a class and encloses their implementation with logging statements. We apply the aspect to a class that has two chained constructors. The program output shows a confusing log where it seems that both constructors have been called in sequence rather than being nested. This paradox is explained because the call to the next constructor happens outside of the code modified by the template.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System;
4
5namespace Doc.OverrideConstructor
6{
7    internal class LogConstructorsAttribute : TypeAspect
8    {
9        public override void BuildAspect( IAspectBuilder<INamedType> builder )
10        {
11            foreach ( var constructor in builder.Target.Constructors )
12            {
13                builder.Advice.Override( constructor, nameof( OverrideConstructorTemplate ) );
14            }
15        }
16
17        [Template]
18        private void OverrideConstructorTemplate()
19        {
20            Console.WriteLine( $"Executing constructor {meta.Target.Constructor}: started" );
21            meta.Proceed();
22            Console.WriteLine( $"Executing constructor {meta.Target.Constructor}: completed" );
23        }
24    }
25}
26
Source Code
1using System;
2
3namespace Doc.OverrideConstructor
4{
5    [LogConstructors]
6    internal class SomeClass
7    {
8        private readonly string _name;
9
10        public SomeClass() : this( "" )
11        {
12            Console.WriteLine( "Within constructor A." );
13        }
14


15        public SomeClass( string name )
16        {
17            this._name = name;
18            Console.WriteLine( "Within constructor B." );

19        }
20    }

21
22    internal static class Program
23    {
24        public static void Main()
25        {
26            _ = new SomeClass();
27        }
28    }
29}
30
Transformed Code
1using System;
2
3namespace Doc.OverrideConstructor
4{
5    [LogConstructors]
6    internal class SomeClass
7    {
8        private readonly string _name;
9
10        public SomeClass() : this("")
11        {
12            Console.WriteLine("Executing constructor SomeClass.SomeClass(): started");
13            Console.WriteLine("Within constructor A.");
14            Console.WriteLine("Executing constructor SomeClass.SomeClass(): completed");
15        }
16
17        public SomeClass(string name)
18        {
19            Console.WriteLine("Executing constructor SomeClass.SomeClass(string): started");
20            this._name = name;
21            Console.WriteLine("Within constructor B.");
22            Console.WriteLine("Executing constructor SomeClass.SomeClass(string): completed");
23        }
24    }
25
26    internal static class Program
27    {
28        public static void Main()
29        {
30            _ = new SomeClass();
31        }
32    }
33}
34
Executing constructor SomeClass.SomeClass(string): started
Within constructor B.
Executing constructor SomeClass.SomeClass(string): completed
Executing constructor SomeClass.SomeClass(): started
Within constructor A.
Executing constructor SomeClass.SomeClass(): completed