La déclaration s'est avérée être un mensonge, un mensonge et une provocation
Mais cela n'avait plus d'importance, car le défi était accepté.
Avertissement
. . . .
Entraînement
Nous créons une chaîne d'héritage. Pour plus de simplicité, nous utiliserons des constructeurs sans paramètre. Dans le constructeur, nous afficherons des informations sur le type et l'identifiant de l'objet sur lequel il est appelé.
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()}");
}
}
Exécutez le programme:
class Program
{
static void Main()
{
new C();
}
}
Et nous obtenons la sortie:
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Digression lyrique
, . , . , . :
:
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
Suppression du code en double
Ajoutez une classe d'assistance:
internal static class Extensions
{
public static void Trace(this object obj) =>
Console.WriteLine($"Type '{obj.GetType().Name}' .ctor called on object #{obj.GetHashCode()}");
}
Et nous remplaçons dans tous les constructeurs
Console.WriteLine($"Type '{nameof(...)}' .ctor called on object #{GetHashCode()}");
sur
this.Trace();
Cependant, le programme sort maintenant: Dans notre cas, l'astuce suivante peut être utilisée. Qui connaît les types de temps de compilation? Compilateur. Il sélectionne également les surcharges de méthode en fonction de ces types. Et pour les types et méthodes génériques, il génère également des entités construites. Par conséquent, nous retournons l'inférence de type correcte en réécrivant la méthode Trace comme suit:
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()}");
Accéder à un constructeur de type de base
C'est là que la réflexion vient à la rescousse. Ajoutez une méthode aux extensions :
public static Action GetBaseConstructor<T>(this T obj) =>
() => typeof(T)
.BaseType
.GetConstructor(Type.EmptyTypes)
.Invoke(obj, Array.Empty<object>());
Ajoutez la propriété aux types B et C :
private Action @base => this.GetBaseConstructor();
Appeler un constructeur de type de base n'importe où
Modifiez le contenu des constructeurs B et C en:
this.Trace();
@base();
Maintenant, la sortie ressemble à ceci:
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
Modification de l'ordre d'appel des constructeurs de type de base
À l'intérieur du type A, créez un type d'assistance:
protected class CtorHelper
{
private CtorHelper() { }
}
Puisque seule la sémantique est importante ici, il est conseillé de rendre le constructeur de type privé. L'instanciation n'a pas de sens. Le type est uniquement destiné à faire la distinction entre les surcharges des constructeurs de type A et celles qui en dérivent. Pour la même raison, le type doit être placé à l'intérieur de A et protégé.
Ajoutez les constructeurs appropriés à A , B et C :
protected A(CtorHelper _) { }
protected B(CtorHelper _) { }
protected C(CtorHelper _) { }
Pour les types B et C, ajoutez un appel à tous les constructeurs:
: base(null)
En conséquence, les classes devraient ressembler à ceci
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();
}
}
Et la sortie devient:
Type 'C' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Le naïf naïf pense que le compilateur a triché
Comprendre le résultat
En ajoutant une méthode aux extensions :
public static void TraceSurrogate<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' surrogate .ctor called on object #{obj.GetHashCode()}");
et en l'appelant dans tous les constructeurs qui acceptent CtorHelper , nous obtenons la sortie suivante : L'ordre des constructeurs selon le principe de base / dérivé, bien sûr, n'a pas changé. Mais tout de même, l'ordre des constructeurs disponibles pour le code client qui portent la charge sémantique a été modifié grâce à la redirection via des appels aux constructeurs helper inaccessibles au 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