Den Basistypkonstruktor überall aufrufen

Ich hatte kürzlich ein Interview und unter anderem gab es eine Frage zur Reihenfolge des Aufrufs von Konstruktoren in C #. Nach der Beantwortung entschied sich der Befragte, seine Gelehrsamkeit zu demonstrieren und erklärte, dass in Java ein Basistypkonstruktor überall in einem abgeleiteten Typkonstruktor aufgerufen werden kann und C # dabei natürlich verliert.



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



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



Bild



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
image



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






All Articles