Memanggil konstruktor tipe dasar di mana saja

Baru-baru ini saya melakukan wawancara, dan antara lain ada pertanyaan tentang urutan pemanggilan konstruktor di C #. Setelah menjawab, orang yang diwawancarai memutuskan untuk mendemonstrasikan ilmunya dan menyatakan bahwa di Java, konstruktor tipe dasar dapat dipanggil di mana saja dalam konstruktor dari tipe turunan, dan C #, tentu saja, kalah dalam hal ini.



Pernyataan itu ternyata bohong, bohong, dan provokasi
image



Tapi sudah tidak masalah lagi, karena tantangan diterima.



gambar



Penolakan
. . . .



Latihan



Kami membuat rantai warisan. Untuk mempermudah, kita akan menggunakan konstruktor tanpa parameter. Dalam konstruktor, kita akan menampilkan informasi tentang tipe dan pengenal dari objek yang dipanggil.



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


Jalankan programnya:



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


Dan kami mendapatkan hasilnya:



Type 'A' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'C' .ctor called on object #58225482



Penyimpangan lirik
, . , . , . :



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


Menghapus kode duplikat



Tambahkan kelas pembantu:



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


Dan kami mengganti di semua konstruktor



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


di



this.Trace();


Namun, program sekarang mengeluarkan: Dalam kasus kami, trik berikut dapat digunakan. Siapa yang tahu tentang jenis waktu kompilasi? Penyusun. Ini juga memilih kelebihan metode berdasarkan jenis ini. Dan untuk tipe dan metode generik, ia juga menghasilkan entitas yang dibangun. Oleh karena itu, kami mengembalikan inferensi tipe yang benar dengan menulis ulang metode Trace sebagai berikut:



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


Mengakses konstruktor tipe dasar



Di sinilah refleksi datang untuk menyelamatkan. Tambahkan metode ke Ekstensi :



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


Tambahkan properti ke tipe B dan C :



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


Memanggil konstruktor tipe dasar di mana saja



Ubah konten konstruktor B dan C menjadi:



this.Trace();
@base();


Sekarang hasilnya terlihat seperti ini:



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



Mengubah Urutan Pemanggil Tipe Konstruktor Dasar



Di dalam tipe A, buat tipe pembantu:



protected class CtorHelper
{
    private CtorHelper() { }
}


Karena hanya semantik yang penting di sini, masuk akal untuk menjadikan konstruktor tipe privat. Instansiasi tidak masuk akal. Jenis ini dimaksudkan hanya untuk membedakan antara beban berlebih dari konstruktor tipe A dan yang diturunkan darinya. Untuk alasan yang sama, tipe harus ditempatkan di dalam A dan dibuat terlindungi.



Tambahkan konstruktor yang sesuai ke A , B dan C :



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


Untuk tipe B dan C, tambahkan panggilan ke semua konstruktor:



: base(null)


Akibatnya, kelas akan terlihat seperti ini
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();
    }
}


Dan hasilnya menjadi:



Type 'C' .ctor called on object #58225482

Type 'B' .ctor called on object #58225482

Type 'A' .ctor called on object #58225482



Orang bodoh yang naif berpikir bahwa kompilator telah curang
image



Memahami hasilnya



Dengan menambahkan metode ke Ekstensi :



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


dan memanggilnya di semua konstruktor yang menerima CtorHelper , kita mendapatkan keluaran berikut : Urutan konstruktor menurut prinsip dasar / turunan, tentu saja, tidak berubah. Tapi tetap saja, urutan konstruktor yang tersedia untuk kode klien yang membawa beban semantik berubah karena pengalihan melalui panggilan ke konstruktor helper yang tidak dapat diakses oleh klien dan tidak melakukan apa pun.



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