La declaración resultó ser una mentira, una mentira y una provocación.
Pero ya no importaba, porque el desafío fue aceptado.
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
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