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.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.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using System;
5
6namespace Doc.OverrideConstructor;
7
8internal class LogConstructorsAttribute : TypeAspect
9{
10 public override void BuildAspect( IAspectBuilder<INamedType> builder )
11 {
12 foreach ( var constructor in builder.Target.Constructors )
13 {
14 builder.With( constructor ).Override( nameof(this.OverrideConstructorTemplate) );
15 }
16 }
17
18 [Template]
19 private void OverrideConstructorTemplate()
20 {
21 Console.WriteLine( $"Executing constructor {meta.Target.Constructor}: started" );
22 meta.Proceed();
23 Console.WriteLine( $"Executing constructor {meta.Target.Constructor}: completed" );
24 }
25}
1using System;
2
3namespace Doc.OverrideConstructor;
4
5[LogConstructors]
6internal 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
22internal static class Program
23{
24 public static void Main()
25 {
26 _ = new SomeClass();
27 }
28}
1using System;
2
3namespace Doc.OverrideConstructor;
4
5[LogConstructors]
6internal 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
26internal static class Program
27{
28 public static void Main()
29 {
30 _ = new SomeClass();
31 }
32}
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