Bagaimana obat generik diimplementasikan?

16

Ini adalah pertanyaan dari perspektif internal kompiler.

Saya tertarik pada obat generik, bukan template (C ++), jadi saya menandai pertanyaan dengan C #. Bukan Java, karena AFAIK generik dalam kedua bahasa berbeda dalam implementasinya.

Ketika saya melihat bahasa tanpa generik, itu sangat mudah, Anda dapat memvalidasi definisi kelas, menambahkannya ke hierarki dan hanya itu.

Tetapi apa yang harus dilakukan dengan kelas generik, dan yang lebih penting bagaimana menangani referensi untuk itu? Cara memastikan bahwa bidang statis adalah singular per instance (yaitu setiap kali parameter generik diselesaikan).

Katakanlah saya melihat panggilan:

var x = new Foo<Bar>();

Apakah saya menambahkan Foo_Barkelas baru ke hierarki?


Pembaruan: Sejauh ini saya hanya menemukan 2 posting yang relevan, tetapi bahkan mereka tidak masuk ke banyak detail dalam arti "bagaimana melakukannya sendiri":

Greenoldman
sumber
Upvoting karena saya pikir jawaban yang lengkap akan menarik. Saya punya beberapa ide tentang cara kerjanya tetapi tidak cukup untuk menjawab secara akurat. Saya tidak berpikir bahwa generik dalam C # mengkompilasi ke kelas khusus untuk setiap jenis generik. Mereka tampaknya diselesaikan pada saat runtime (mungkin ada hit kecepatan yang terlihat dari menggunakan obat generik). Mungkin kita bisa membuat Eric Lippert berpadu?
KChaloux
2
@KChaloux: Di tingkat MSIL, ada satu deskripsi generik. Ketika JIT berjalan, itu menciptakan kode mesin terpisah untuk setiap jenis nilai yang digunakan sebagai parameter umum, dan satu set kode mesin lagi yang mencakup semua jenis referensi. Mempertahankan deskripsi umum dalam MSIL benar-benar bagus karena memungkinkan Anda untuk membuat instance baru saat runtime.
Ben Voigt
@Ben Itu sebabnya saya tidak berusaha untuk benar-benar menjawab pertanyaan: p
KChaloux
Saya tidak yakin jika Anda masih di sekitar, tapi apa bahasa yang Anda kompilasi untuk . Itu akan memiliki banyak pengaruh pada bagaimana Anda menerapkan obat generik. Saya dapat memberikan informasi tentang bagaimana saya biasanya mendekatinya di ujung depan, tetapi ujung belakang bisa sangat bervariasi.
Telastyn
@ Telastyn, untuk topik-topik yang pasti saya :-) Saya mencari sesuatu yang sangat dekat dengan C #, dalam kasus saya, saya mengkompilasi ke PHP (tidak ada lelucon). Saya akan berterima kasih jika Anda berbagi pengetahuan Anda.
greenoldman

Jawaban:

4

Cara memastikan bahwa bidang statis adalah singular per instance (yaitu setiap kali parameter generik diselesaikan).

Setiap instantiasi generik memiliki salinan sendiri dari MethodTable (dinamai membingungkan), yang merupakan tempat bidang statis disimpan.

Katakanlah saya melihat panggilan:

var x = new Foo<Bar>();

Apakah saya menambahkan yang baru Foo_Barkelas ke hierarki?

Saya tidak yakin itu berguna untuk memikirkan hirarki kelas sebagai beberapa struktur yang sebenarnya ada saat runtime, itu lebih merupakan konstruksi logis.

Tetapi jika Anda mempertimbangkan MethodTable, masing-masing dengan pointer tidak langsung ke kelas dasarnya, untuk membentuk hierarki ini, maka ya, ini menambahkan kelas baru ke hierarki.

svick
sumber
Terima kasih, itu bagian yang menarik. Jadi bidang statis diselesaikan mirip dengan tabel virtual, bukan? Ada referensi ke kamus "global" yang menyimpan entri per setiap jenis? Jadi saya bisa memiliki 2 majelis yang tidak mengetahui satu sama lain menggunakan Foo<string>dan mereka tidak akan menghasilkan dua contoh bidang statis Foo.
greenoldman
1
@greenoldman Ya, tidak mirip dengan tabel virtual, persis sama. MethodTable menampung bidang statis dan referensi untuk metode tipe, yang digunakan dalam pengiriman virtual (itulah sebabnya disebut MethodTable). Dan ya, CLR harus memiliki beberapa tabel yang dapat digunakan untuk mengakses semua MethodTables.
svick
2

Saya melihat dua pertanyaan konkret yang sebenarnya di sana. Anda mungkin ingin mengajukan pertanyaan terkait lainnya (sebagai pertanyaan terpisah dengan tautan kembali ke pertanyaan ini) untuk mendapatkan pemahaman penuh.

Bagaimana bidang statis diberikan instance terpisah per instance generik?

Nah, untuk anggota statis yang tidak terkait dengan parameter tipe generik, ini cukup mudah (gunakan kamus yang dipetakan dari parameter generik ke nilai).

Anggota (statis atau tidak) yang terkait dengan parameter tipe dapat ditangani melalui penghapusan tipe. Cukup gunakan apa pun kendala terkuat (sering System.Object). Karena informasi jenis dihapus setelah pemeriksaan jenis kompiler, itu berarti bahwa pemeriksaan tipe runtime tidak diperlukan (walaupun antarmuka gips mungkin masih ada saat runtime).

Apakah setiap instance generik muncul secara terpisah dalam hierarki tipe?

Tidak dalam .NET generics. Keputusan dibuat untuk mengecualikan pewarisan dari parameter tipe, jadi ternyata semua instance generik menempati tempat yang sama dalam hierarki tipe.

Ini mungkin keputusan yang bagus, karena kegagalan mencari nama dari kelas dasar akan sangat mengejutkan.

Ben Voigt
sumber
Masalah saya adalah saya tidak bisa lepas dari pemikiran dalam hal template. Misalnya - template tidak seperti kelas generik adalah sepenuhnya disusun. Ini berarti bahwa di majelis lain yang menggunakan kelas ini apa yang terjadi? Metode yang sudah dikompilasi disebut dengan casting internal? Saya ragu obat generik dapat mengandalkan kendala - lebih pada argumen, jika tidak Foo<int>dan Foo<string>akan memukul data yang sama dengan Fookendala w / o.
greenoldman
1
@greenoldman: Bisakah kita menghindari tipe nilai sebentar, karena sebenarnya ditangani secara khusus? Jika Anda memiliki List<string>dan List<Form>, maka karena secara List<T>internal memiliki anggota tipe T[]dan tidak ada kendala T, maka apa yang akan Anda dapatkan adalah kode mesin yang memanipulasi object[]. Namun, karena hanya Tinstance yang dimasukkan ke dalam array, semua yang keluar dapat dikembalikan sebagai Ttanpa tipe check tambahan. Di sisi lain, jika Anda punya ControlCollection<T> where T : Control, maka array internal T[]akan menjadi Control[].
Ben Voigt
Apakah saya mengerti dengan benar, bahwa kendala diambil dan digunakan sebagai nama ketik internal, tetapi ketika kelas benar-benar digunakan casting digunakan? OK, saya mengerti model itu, tapi saya mendapat kesan Java menggunakannya, bukan C #.
greenoldman
3
@greenoldman: Java melakukan penghapusan tipe pada langkah terjemahan source-> bytecode. Yang membuatnya tidak mungkin bagi verifikasi untuk memverifikasi kode generik. C # melakukannya pada langkah kode mesin bytecode->.
Ben Voigt
@BenVoigt Beberapa informasi disimpan di Jawa tentang tipe generik, karena jika tidak, Anda tidak dapat mengkompilasi terhadap kelas penggunaan generik tanpa sumbernya. Hanya saja tidak disimpan dalam urutan bytecode itu sendiri AIUI, melainkan dalam metadata kelas.
Donal Fellows
1

Tetapi apa yang harus dilakukan dengan kelas generik, dan yang lebih penting bagaimana menangani referensi untuk itu?

Cara umum di ujung depan kompiler adalah memiliki dua jenis instance, tipe generik ( List<T>) dan tipe generik terikat ( List<Foo>). Tipe generik menentukan fungsi apa yang ada, bidang apa, dan memiliki referensi tipe generik di mana pun Tdigunakan. Tipe generik terikat berisi referensi ke tipe generik, dan seperangkat argumen tipe. Itu memiliki informasi yang cukup bagi Anda untuk kemudian menghasilkan tipe konkret, mengganti referensi tipe generik dengan Fooatau apa pun jenis argumennya. Perbedaan semacam ini penting ketika Anda melakukan inferensi tipe dan perlu disimpulkan List<T>dibandingkan List<Foo>.

Alih-alih memikirkan generik seperti templat (yang membangun berbagai implementasi secara langsung), mungkin akan lebih membantu untuk menganggapnya sebagai konstruktor tipe bahasa fungsional (di mana argumen generik seperti argumen menjadi fungsi yang memberi Anda tipe).

Sedangkan untuk bagian belakang, saya tidak benar-benar tahu. Semua pekerjaan saya dengan obat-obatan generik menargetkan CIL sebagai backend, jadi saya bisa mengkompilasinya menjadi obat generik yang didukung di sana.

Telastyn
sumber
Terima kasih banyak (Sayang sekali saya tidak bisa menerima jawaban yang berlipat ganda). Sangat menyenangkan untuk mendengar bahwa saya melakukan langkah itu dengan benar - dalam kasus saya List<T>memiliki tipe nyata (definisi), sementara List<Foo>(terima kasih atas bagian terminologi juga) dengan pendekatan saya memegang deklarasi List<T>(tentu saja sekarang terikat pada Foobukannya T).
greenoldman