Programmer seperti saya, yang datang ke C # dengan pengalaman luas di Delphi, sering tidak memiliki apa yang disebut Delphi referensi kelas, dan dalam karya teoretis, metaclass. Beberapa kali di berbagai forum saya menemukan diskusi yang berlangsung sepanjang jalur yang sama. Itu dimulai dengan pertanyaan dari seorang mantan lumba-lumba tentang bagaimana membuat metaclass di C #. Sharpis tidak mengerti masalah ini, mencoba untuk menjelaskan jenis binatang apa ini - metaclass, lumba-lumba seperti yang bisa mereka jelaskan, tetapi penjelasannya singkat dan tidak lengkap, dan akibatnya, para penajam benar-benar bingung mengapa semua ini diperlukan. Lagi pula, hal yang sama dapat dilakukan dengan bantuan pabrik refleksi dan kelas.
Pada artikel ini, saya akan mencoba memberi tahu Anda apa itu metaclasses bagi mereka yang belum pernah melihatnya. Selanjutnya, biarkan semua orang memutuskan sendiri apakah akan baik untuk memiliki hal seperti itu dalam bahasa, atau cukup refleksi. Yang saya tulis di sini hanyalah fantasi tentang bagaimana jadinya jika ada benar-benar metaclasses di C #. Semua contoh dalam artikel ini ditulis dalam versi hipotetis C # ini, tidak ada kompiler tunggal yang ada saat ini dapat mengkompilasi mereka.
Apa itu metaclass?
Jadi, apa itu metaclass? Ini adalah tipe khusus yang berfungsi untuk menggambarkan tipe lainnya. Ada sesuatu yang sangat mirip di C # - tipe Type. Namun hanya serupa. Nilai tipe Type dapat menggambarkan tipe apa pun, metaclass hanya bisa menggambarkan ahli waris dari kelas yang ditentukan ketika metaclass dideklarasikan.
Untuk melakukan ini, versi hipotetis kami dari C # mendapatkan tipe Type <T>, yang merupakan penerus Type. Tetapi Tipe <T> hanya cocok untuk menggambarkan tipe T atau turunannya.
Saya akan menjelaskan ini dengan sebuah contoh:
class A { } class A2 : A { } class B { } static class Program { static void Main() { Type<A> ta; ta = typeof(A);
Contoh di atas adalah langkah pertama untuk munculnya metaclasses. Jenis Jenis <T> memungkinkan Anda untuk membatasi jenis apa yang dapat dijelaskan oleh nilai yang sesuai. Fitur ini mungkin terbukti berguna dalam dirinya sendiri, tetapi kemungkinan metaclasses tidak terbatas pada ini.
Metaclasses dan anggota kelas statis
Jika beberapa kelas X memiliki anggota statis, maka Tipe metaclass <X> mendapatkan anggota yang sama, tidak lagi statis, di mana Anda dapat mengakses anggota statis X. Mari kita jelaskan frasa membingungkan ini dengan sebuah contoh.
class X { public static void DoSomething() { } } static class Program { static void Main() { Type<X> tx = typeof(X); tx.DoSomething();
Di sini, secara umum, muncul pertanyaan - bagaimana jika di kelas X metode statis dideklarasikan, set nama dan parameter yang bertepatan dengan set nama dan parameter salah satu metode dari kelas Type, pewarisnya adalah Type <X>? Ada beberapa opsi yang cukup sederhana untuk menyelesaikan masalah ini, tetapi saya tidak akan membahasnya - untuk kesederhanaan kami percaya bahwa dalam bahasa fantasi konflik kami tidak ada nama ajaib.Kode di atas untuk orang normal mana pun harus membingungkan - mengapa kita memerlukan variabel untuk memanggil metode jika kita dapat memanggil metode ini secara langsung? Memang, dalam bentuk ini, peluang ini tidak berguna. Tetapi manfaatnya datang ketika Anda menambahkan metode kelas untuk itu.
Metode kelas
Metode kelas adalah konstruk lain yang dimiliki Delphi, tetapi tidak ada dalam C #. Ketika dideklarasikan, metode ini ditandai dengan kelas kata dan merupakan persilangan antara metode statis dan metode instance. Seperti metode statis, mereka tidak terikat pada instance tertentu dan dapat dipanggil melalui nama kelas tanpa membuat instance. Tapi, tidak seperti metode statis, mereka memiliki parameter implisit ini. Hanya ini dalam kasus ini bukan turunan dari kelas, tetapi metaclass, yaitu jika metode kelas dijelaskan dalam kelas X, maka parameter ini akan menjadi tipe Ketik <X>. Dan Anda dapat menggunakannya seperti ini:
class X { public class void Report() { Console.WriteLine($” {this.Name}”); } } class Y : X { } static class Program { static void Main() { X.Report()
Fitur ini tidak terlalu mengesankan sejauh ini. Tetapi berkat itu, metode kelas, tidak seperti metode statis, bisa virtual. Lebih tepatnya, metode statis juga bisa dibuat virtual, tetapi tidak jelas apa yang harus dilakukan selanjutnya dengan virtualitas ini. Tetapi dengan metode kelas, masalah seperti itu tidak muncul. Pertimbangkan ini dengan sebuah contoh.
class X { protected static virtual DoReport() { Console.WriteLine(“!”); } public static Report() { DoReport(); } } class Y : X { protected static override DoReport() { Consloe.WriteLine(“!”); } } static class Program { static void Main() { X.Report()
Dengan logika beberapa hal, saat memanggil Y.Laporan, "Sampai jumpa!" Harus ditampilkan. Tetapi metode X.Report tidak memiliki informasi tentang dari mana kelas itu dipanggil, sehingga tidak dapat secara dinamis memilih antara X.DoReport dan Y.DoReport. Akibatnya, X.Report akan selalu memanggil X.DoReport, bahkan jika Laporan dipanggil melalui Y. Tidak ada gunanya membuat metode DoReport virtual. Oleh karena itu, C # tidak memungkinkan membuat metode statis virtual - akan mungkin menjadikannya virtual, tetapi Anda tidak akan mendapat manfaat dari keutamaan mereka.
Hal lain adalah metode kelas. Jika Laporan dalam contoh sebelumnya tidak statis, tetapi kelas, itu akan "tahu" kapan dipanggil melalui X, dan kapan melalui Y. Dengan demikian, kompiler dapat menghasilkan kode yang akan memilih DoReport yang diinginkan, dan panggilan ke Y.Report akan menghasilkan sampai pada kesimpulan "Sampai jumpa!".
Fitur ini berguna dalam dirinya sendiri, tetapi menjadi lebih berguna jika Anda menambahkan kemampuan untuk memanggil variabel kelas melalui metaclasses. Sesuatu seperti ini:
class X { public static virtual Report() { Console.WriteLine(“!”); } } class Y : X { public static override Report() { Consloe.WriteLine(“!”); } } static class Program { static void Main() { Type<X> tx = typeof(X); tx.Report()
Untuk mencapai polimorfisme seperti itu tanpa metaclasses dan metode kelas virtual, untuk kelas X dan setiap turunannya harus menulis kelas bantu dengan metode virtual yang biasa. Ini membutuhkan upaya yang jauh lebih besar, dan kontrol oleh kompiler tidak akan begitu lengkap, yang meningkatkan kemungkinan membuat kesalahan di suatu tempat. Sementara itu, situasi ketika polimorfisme diperlukan pada level tipe, dan bukan pada level instance, ditemui secara teratur, dan jika bahasa mendukung polimorfisme seperti itu, ini adalah properti yang sangat berguna.
Konstruktor virtual
Jika metaclasses muncul dalam bahasa, maka konstruktor virtual perlu ditambahkan padanya. Jika konstruktor virtual dideklarasikan dalam kelas, maka semua turunannya harus tumpang tindih, mis. memiliki konstruktor Anda sendiri dengan set parameter yang sama, misalnya:
class A { public virtual A(int x, int y) { ... } } class B : A { public override B(int x, int y) : base(x, y) { } } class C : A { public C(int z) { ... } }
Dalam kode ini, kelas C tidak boleh dikompilasi, karena tidak memiliki konstruktor dengan parameter int x, int y, tetapi kelas B dikompilasi tanpa kesalahan.
Opsi lain dimungkinkan: jika konstruktor virtual leluhur tidak tumpang tindih dalam pewaris, kompiler secara otomatis tumpang tindih, seperti sekarang ini secara otomatis membuat konstruktor default. Kedua pendekatan memiliki pro dan kontra yang jelas, tetapi ini tidak penting untuk gambaran keseluruhan.Konstruktor virtual dapat digunakan di mana pun konstruktor biasa dapat digunakan. Selain itu, jika kelas memiliki konstruktor virtual, metaclass-nya memiliki metode CreateInstance dengan set parameter yang sama dengan konstruktor, dan metode ini akan membuat instance kelas, seperti yang ditunjukkan pada contoh di bawah ini.
class A { public virtual A(int x, int y) { ... } } class B : A { public override B(int x, int y) : base(x, y) { } } static class Program { static void Main() { Type<A> ta = typeof(A); A a1 = ta.CreateInstance(10, 12);
Dengan kata lain, kita mendapat kesempatan untuk membuat objek yang tipenya ditentukan pada saat run time. Sekarang ini juga dapat dilakukan dengan menggunakan Activator.CreateInstance. Tetapi metode ini bekerja melalui refleksi, sehingga kebenaran dari set parameter diperiksa hanya pada tahap eksekusi. Tetapi jika kita memiliki metaclasses, maka kode dengan parameter yang salah tidak akan dikompilasi. Selain itu, ketika menggunakan refleksi, kecepatan pekerjaan menyisakan banyak yang diinginkan, dan metaclasses memungkinkan Anda untuk meminimalkan biaya.
Kesimpulan
Saya selalu terkejut mengapa Halesberg, yang merupakan pengembang utama dari Delphi dan C #, tidak membuat metaclasses dalam C #, meskipun mereka membuktikan diri mereka dengan sangat baik di Delphi. Mungkin intinya di sini adalah bahwa dalam Delphi (dalam versi-versi yang Halesberg lakukan) hampir tidak ada refleksi, dan tidak ada alternatif untuk metaclasses, yang tidak dapat dikatakan tentang C #. Memang, semua contoh dari artikel ini tidak begitu sulit untuk diulang, hanya menggunakan alat-alat yang sudah ada dalam bahasa. Tetapi semua ini akan bekerja lebih lambat dari pada metaclasses, dan kebenaran panggilan akan diperiksa saat runtime, bukan kompilasi. Jadi pendapat pribadi saya adalah bahwa C # akan sangat bermanfaat jika metaclasses muncul di dalamnya.