The statement turned out to be a lie, a lie and a provocation
But it didn't matter anymore, because the challenge was accepted.
Disclaimer
. . . .
Training
We create an inheritance chain. For simplicity, we will use parameterless constructors. In the constructor, we will display information about the type and the identifier of the object on which it is called.
public class A
{
public A()
{
Console.WriteLine($"Type '{nameof(A)}' .ctor called on object #{GetHashCode()}");
}
}
public class B : A
{
public B()
{
Console.WriteLine($"Type '{nameof(B)}' .ctor called on object #{GetHashCode()}");
}
}
public class C : B
{
public C()
{
Console.WriteLine($"Type '{nameof(C)}' .ctor called on object #{GetHashCode()}");
}
}
Run the program:
class Program
{
static void Main()
{
new C();
}
}
And we get the output:
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Lyrical digression
, . , . , . :
:
public A() : this() { } // CS0516 Constructor 'A.A()' cannot call itself
:
public A() : this(new object()) { }
public A(object _) : this(0) { }
public A(int _) : this() { } // CS0768 Constructor 'A.A(int)' cannot call itself through another constructor
Removing duplicate code
Add a helper class:
internal static class Extensions
{
public static void Trace(this object obj) =>
Console.WriteLine($"Type '{obj.GetType().Name}' .ctor called on object #{obj.GetHashCode()}");
}
And we replace in all constructors
Console.WriteLine($"Type '{nameof(...)}' .ctor called on object #{GetHashCode()}");
on
this.Trace();
However, the program now outputs: In our case, the following trick can be used. Who knows about compile time types? Compiler. It also selects method overloads based on these types. And for generic types and methods it generates constructed entities too. Therefore, we return the correct type inference by rewriting the Trace method as follows:
Type 'C' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
public static void Trace<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}");
Accessing a base type constructor
This is where reflection comes to the rescue. Add a method to Extensions :
public static Action GetBaseConstructor<T>(this T obj) =>
() => typeof(T)
.BaseType
.GetConstructor(Type.EmptyTypes)
.Invoke(obj, Array.Empty<object>());
Add the property to types B and C :
private Action @base => this.GetBaseConstructor();
Calling a base type constructor anywhere
Change the contents of constructors B and C to:
this.Trace();
@base();
Now the output looks like this:
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Changing the Order of Calling Base Type Constructors
Inside type A, create a helper type:
protected class CtorHelper
{
private CtorHelper() { }
}
Since only semantics are important here, it makes sense to make the type constructor private. Instantiation doesn't make sense. The type is intended solely to distinguish between overloads of type A constructors and those derived from it. For the same reason, the type should be placed inside A and made protected.
Add the appropriate constructors to A , B and C :
protected A(CtorHelper _) { }
protected B(CtorHelper _) { }
protected C(CtorHelper _) { }
For types B and C, add a call to all constructors:
: base(null)
As a result, the classes should look like this
internal static class Extensions
{
public static Action GetBaseConstructor<T>(this T obj) =>
() => typeof(T)
.BaseType
.GetConstructor(Type.EmptyTypes)
.Invoke(obj, Array.Empty<object>());
public static void Trace<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}");
}
public class A
{
protected A(CtorHelper _) { }
public A()
{
this.Trace();
}
protected class CtorHelper
{
private CtorHelper() { }
}
}
public class B : A
{
private Action @base => this.GetBaseConstructor();
protected B(CtorHelper _) : base(null) { }
public B() : base(null)
{
this.Trace();
@base();
}
}
public class C : B
{
private Action @base => this.GetBaseConstructor();
protected C(CtorHelper _) : base(null) { }
public C() : base(null)
{
this.Trace();
@base();
}
}
And the output becomes:
Type 'C' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
The naive simpleton thinks that the compiler has cheated
Understanding the result
By adding a method to Extensions :
public static void TraceSurrogate<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' surrogate .ctor called on object #{obj.GetHashCode()}");
and calling it in all constructors accepting CtorHelper , we will get the following output: The order of the constructors according to the base / derived principle, of course, has not changed. But still, the order of the constructors available to the client code that carry semantic load was changed thanks to the redirection through calls to the helper constructors inaccessible to the client.
Type 'A' surrogate .ctor called on object #58225482
Type 'B' surrogate .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Type 'A' surrogate .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482