Calling the base type constructor anywhere

I had an interview recently, and among others there was a question about the order of calling constructors in C #. After answering, the interviewee decided to demonstrate his erudition and stated that in Java, the constructor of a base type can be called anywhere in the constructor of a derived type, and C #, of course, loses in this.



The statement turned out to be a lie, a lie and a provocation
image



But it didn't matter anymore, because the challenge was accepted.



image



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
image



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






All Articles