Die Aussage erwies sich als Lüge, Lüge und Provokation

Aber es war nicht mehr wichtig, denn die Herausforderung wurde angenommen.

Haftungsausschluss
. . . .
Ausbildung
Wir erstellen eine Vererbungskette. Der Einfachheit halber werden parameterlose Konstruktoren verwendet. Im Konstruktor werden Informationen zum Typ und zur Kennung des Objekts angezeigt, für das es aufgerufen wird.
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()}");
}
}
Führen Sie das Programm aus:
class Program
{
static void Main()
{
new C();
}
}
Und wir bekommen die Ausgabe:
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Lyrischer Exkurs
, . , . , . :
:
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
Doppelten Code entfernen
Fügen Sie eine Hilfsklasse hinzu:
internal static class Extensions
{
public static void Trace(this object obj) =>
Console.WriteLine($"Type '{obj.GetType().Name}' .ctor called on object #{obj.GetHashCode()}");
}
Und wir ersetzen in allen Konstruktoren
Console.WriteLine($"Type '{nameof(...)}' .ctor called on object #{GetHashCode()}");
auf
this.Trace();
Das Programm gibt jedoch jetzt Folgendes aus : In unserem Fall kann der folgende Trick verwendet werden. Wer kennt sich mit Kompilierzeittypen aus? Compiler. Außerdem werden Methodenüberladungen basierend auf diesen Typen ausgewählt. Und für generische Typen und Methoden werden auch konstruierte Entitäten generiert. Daher geben wir die korrekte Typinferenz zurück, indem wir die Trace- Methode wie folgt umschreiben :
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()}");
Zugriff auf einen Basistypkonstruktor
Hier kommt die Reflexion zur Rettung. Fügen Sie Erweiterungen eine Methode hinzu:
public static Action GetBaseConstructor<T>(this T obj) =>
() => typeof(T)
.BaseType
.GetConstructor(Type.EmptyTypes)
.Invoke(obj, Array.Empty<object>());
Fügen Sie die Eigenschaft den Typen B und C hinzu :
private Action @base => this.GetBaseConstructor();
Überall einen Basistypkonstruktor aufrufen
Ändern Sie den Inhalt der Konstruktoren B und C in:
this.Trace();
@base();
Jetzt sieht die Ausgabe folgendermaßen aus:
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
Ändern der Aufrufreihenfolge von Basistypkonstruktoren
Erstellen Sie in Typ A einen Hilfstyp:
protected class CtorHelper
{
private CtorHelper() { }
}
Da hier nur die Semantik wichtig ist, ist es sinnvoll, den Typkonstruktor privat zu machen. Instanziierung macht keinen Sinn. Der Typ soll ausschließlich zwischen Überladungen von Typ- A- Konstruktoren und von diesen abgeleiteten unterscheiden. Aus dem gleichen Grund sollte der Typ in A platziert und geschützt werden.
Fügen Sie die entsprechenden Konstruktoren zu A , B und C hinzu :
protected A(CtorHelper _) { }
protected B(CtorHelper _) { }
protected C(CtorHelper _) { }
Fügen Sie für die Typen B und C allen Konstruktoren einen Aufruf hinzu:
: base(null)
Daher sollten die Klassen so aussehen
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();
}
}
Und die Ausgabe wird:
Type 'C' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Der naive Simpleton glaubt, dass der Compiler betrogen hat

Das Ergebnis verstehen
Durch Hinzufügen einer Methode zu Erweiterungen :
public static void TraceSurrogate<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' surrogate .ctor called on object #{obj.GetHashCode()}");
Wenn wir es in allen Konstruktoren aufrufen, die CtorHelper akzeptieren , erhalten wir die folgende Ausgabe: Die Reihenfolge der Konstruktoren nach dem Basis- / Ableitungsprinzip hat sich natürlich nicht geändert. Die Reihenfolge der Konstruktoren, die dem Clientcode zur Verfügung stehen und die semantische Last tragen, wurde jedoch aufgrund der Umleitung durch Aufrufe an die Hilfskonstruktoren geändert, auf die der Client nicht zugreifen kann und die nichts tun.
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