Llamar al constructor de tipo base en cualquier lugar

Recientemente tuve una entrevista y, entre otras, hubo una pregunta sobre el orden de llamar a los constructores en C #. Después de responder, el entrevistado decidió demostrar su erudición y afirmó que en Java, un constructor de tipo base se puede llamar en cualquier lugar de un constructor de tipo derivado, y C #, por supuesto, pierde en esto.



La declaración resultó ser una mentira, una mentira y una provocación.
image



Pero ya no importaba, porque el desafío fue aceptado.



imagen



Descargo de responsabilidad
. . . .



Formación



Creamos una cadena de herencia. Para simplificar, utilizaremos constructores sin parámetros. En el constructor, mostraremos información sobre el tipo y el identificador del objeto en el que se llama.



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()}");
    }
}


Ejecuta el programa:



class Program
{
    static void Main()
    {
        new C();
    }
}


Y obtenemos el resultado:



Type 'A' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'C' .ctor called on object #58225482



Digresión lírica
, . , . , . :



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


Eliminar código duplicado



Agrega una clase de ayuda:



internal static class Extensions
{
    public static void Trace(this object obj) =>
        Console.WriteLine($"Type '{obj.GetType().Name}' .ctor called on object #{obj.GetHashCode()}");
}


Y reemplazamos en todos los constructores



Console.WriteLine($"Type '{nameof(...)}' .ctor called on object #{GetHashCode()}");


en



this.Trace();


Sin embargo, el programa ahora muestra: En nuestro caso, se puede usar el siguiente truco. ¿Quién sabe sobre los tipos de tiempo de compilación? Compilador. También selecciona sobrecargas de métodos en función de estos tipos. Y para los tipos y métodos genéricos, también genera entidades construidas. Por lo tanto, devolvemos la inferencia de tipo correcta reescribiendo el método Trace de la siguiente manera:



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()}");


Accediendo a un constructor de tipo base



Aquí es donde la reflexión viene al rescate. Agregue un método a las extensiones :



public static Action GetBaseConstructor<T>(this T obj) =>
    () => typeof(T)
          .BaseType
          .GetConstructor(Type.EmptyTypes)
          .Invoke(obj, Array.Empty<object>());


Agregue la propiedad a los tipos B y C :



private Action @base => this.GetBaseConstructor();


Llamar a un constructor de tipo base en cualquier lugar



Cambie el contenido de los constructores B y C a:



this.Trace();
@base();


Ahora la salida se ve así:



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



Cambiar el orden de llamada de los constructores de tipo base



Dentro del tipo A, cree un tipo de ayudante:



protected class CtorHelper
{
    private CtorHelper() { }
}


Dado que solo la semántica es importante aquí, tiene sentido hacer que el constructor de tipos sea privado. La instanciación no tiene sentido. El tipo está destinado únicamente a distinguir entre las sobrecargas de los constructores de tipo A y las derivadas de él. Por la misma razón, el tipo debe colocarse dentro de A y protegerse.



Agregue los constructores apropiados a A , B y C :



protected A(CtorHelper _) { }
protected B(CtorHelper _) { }
protected C(CtorHelper _) { }


Para los tipos B y C, agregue una llamada a todos los constructores:



: base(null)


Como resultado, las clases deberían verse así
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();
    }
}


Y la salida se convierte en:



Type 'C' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482



El ingenuo simplón piensa que el compilador ha hecho trampa
image



Entendiendo el resultado



Añadiendo un método a las extensiones :



public static void TraceSurrogate<T>(this T obj) =>
    Console.WriteLine($"Type '{typeof(T).Name}' surrogate .ctor called on object #{obj.GetHashCode()}");


y llamándolo en todos los constructores que aceptan CtorHelper , obtenemos el siguiente resultado: El orden de los constructores según el principio base / derivado, por supuesto, no ha cambiado. Pero aún así, el orden de los constructores disponibles para el código del cliente que llevan carga semántica se cambió gracias a la redirección a través de llamadas a los constructores auxiliares inaccesibles para el cliente.



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