Saya tahu bahwa struct di .NET tidak mendukung pewarisan, tetapi tidak begitu jelas mengapa mereka dibatasi dengan cara ini.
Alasan teknis apa yang mencegah struct mewarisi dari struct lain?
.net
inheritance
struct
Juliet
sumber
sumber
Jawaban:
Alasan tipe nilai tidak dapat mendukung pewarisan adalah karena array.
Masalahnya adalah, untuk alasan kinerja dan GC, array tipe nilai disimpan "inline". Misalnya,
new FooType[10] {...}
jikaFooType
merupakan tipe referensi, 11 objek akan dibuat pada heap terkelola (satu untuk array, dan 10 untuk setiap instance tipe). JikaFooType
merupakan tipe nilai, hanya satu instance yang akan dibuat pada heap terkelola - untuk array itu sendiri (karena setiap nilai array akan disimpan "sebaris" dengan array).Sekarang, misalkan kita memiliki warisan dengan tipe nilai. Jika digabungkan dengan perilaku array "penyimpanan inline" di atas, Hal-hal Buruk akan terjadi, seperti yang dapat dilihat di C ++ .
Pertimbangkan kode pseudo-C # ini:
Dengan aturan konversi normal, a
Derived[]
dapat dikonversi menjadi aBase[]
(lebih baik atau lebih buruk), jadi jika Anda s / struct / class / g untuk contoh di atas, itu akan dikompilasi dan berjalan seperti yang diharapkan, tanpa masalah. Tetapi jikaBase
danDerived
adalah tipe nilai, dan array menyimpan nilai secara inline, maka kita memiliki masalah.Kami memiliki masalah karena
Square()
tidak tahu apa-apaDerived
, itu hanya akan menggunakan aritmatika pointer untuk mengakses setiap elemen array, bertambah dengan jumlah konstan (sizeof(A)
). Sidang akan samar-samar seperti:(Ya, itu adalah rakitan yang menjijikkan, tetapi intinya adalah kita akan menambah melalui array pada konstanta waktu kompilasi yang diketahui, tanpa mengetahui bahwa tipe turunan sedang digunakan.)
Jadi, jika ini benar-benar terjadi, kami akan mengalami masalah kerusakan memori. Secara khusus, di dalam
Square()
, sebenarnyavalues[1].A*=2
akan berubah !values[0].B
Coba debug ITU !
sumber
Bayangkan struct mendukung warisan. Kemudian menyatakan:
berarti variabel struct tidak memiliki ukuran tetap, dan itulah mengapa kami memiliki tipe referensi.
Lebih baik lagi, pertimbangkan ini:
sumber
Foo
inheritBar
seharusnya tidak mengizinkan aFoo
untuk ditugaskan ke aBar
, tetapi mendeklarasikan struct seperti itu dapat memungkinkan beberapa efek yang berguna: (1) Buat anggota dengan nama khusus dari tipeBar
sebagai item pertamaFoo
, danFoo
sertakan nama anggota yang alias ke anggota tersebutBar
, mengizinkan kode yang telah digunakanBar
untuk diadaptasi untuk menggunakan aFoo
, tanpa harus mengganti semua referensithing.BarMember
denganthing.theBar.BarMember
, dan mempertahankan kemampuan untuk membaca dan menulis semuaBar
bidang sebagai grup; ...Struktur tidak menggunakan referensi (kecuali jika diberi kotak, tetapi Anda harus mencoba menghindarinya) sehingga polimorfisme tidak bermakna karena tidak ada tipuan melalui penunjuk referensi. Objek biasanya berada di heap dan direferensikan melalui pointer referensi, tetapi struct dialokasikan pada stack (kecuali jika dimasukkan dalam kotak) atau dialokasikan "di dalam" memori yang ditempati oleh tipe referensi di heap.
sumber
Foo
yang memiliki bidang tipe strukturBar
dapat menganggapBar
anggotanya sebagai miliknya, sehingga sebuahPoint3d
kelas misalnya bisa merangkum aPoint2d xy
tetapi merujuk keX
bidang itu sebagai salah satuxy.X
atauX
.Inilah yang dikatakan dokumen :
Pada dasarnya, mereka seharusnya menyimpan data sederhana dan karenanya tidak memiliki "fitur tambahan" seperti warisan. Mungkin secara teknis mungkin bagi mereka untuk mendukung beberapa jenis warisan terbatas (bukan polimorfisme, karena mereka berada di tumpukan), tetapi saya percaya ini juga merupakan pilihan desain untuk tidak mendukung warisan (seperti banyak hal lain di .NET bahasa adalah.)
Di sisi lain, saya setuju dengan manfaat warisan, dan saya pikir kita semua telah mencapai titik di mana kita ingin
struct
mewarisi dari orang lain, dan menyadari bahwa itu tidak mungkin. Tetapi pada titik itu, struktur datanya mungkin sangat maju sehingga harus tetap menjadi kelas.sumber
Point3D
dari aPoint2D
; Anda tidak akan dapat menggunakan aPoint3D
alih - alih aPoint2D
, tetapi Anda tidak perlu menerapkan ulangPoint3D
seluruhnya dari awal.) Begitulah cara saya menafsirkannya ...class
padastruct
saat yang tepat.Class seperti inheritance tidak dimungkinkan, karena struct diletakkan langsung di stack. Struct yang mewarisi akan lebih besar dari induknya, tetapi JIT tidak mengetahuinya, dan mencoba untuk menempatkan terlalu banyak ruang terlalu sedikit. Kedengarannya agak tidak jelas, mari kita tulis contoh:
Jika ini memungkinkan, itu akan mogok pada cuplikan berikut:
Ruang dialokasikan untuk ukuran A, bukan untuk ukuran B.
sumber
Ada satu hal yang ingin saya perbaiki. Meskipun alasan struct tidak dapat diwariskan adalah karena mereka tinggal di tumpukan adalah yang benar, itu adalah penjelasan yang setengah benar. Structs, seperti jenis nilai lainnya, dapat hidup di tumpukan. Karena itu akan tergantung di mana variabel dideklarasikan, mereka akan tinggal di tumpukan atau di heap . Ini akan terjadi ketika mereka masing-masing adalah variabel lokal atau bidang contoh.
Mengatakan itu, Cecil Memiliki Nama yang tepat.
Saya ingin menekankan hal ini, tipe nilai dapat hidup di tumpukan. Ini tidak berarti mereka selalu melakukannya. Variabel lokal, termasuk parameter metode, akan. Yang lainnya tidak. Namun demikian, itu tetap menjadi alasan mereka tidak dapat diwariskan. :-)
sumber
Struktur dialokasikan di tumpukan. Ini berarti nilai semantiknya cukup gratis, dan mengakses anggota struct sangat murah. Ini tidak mencegah polimorfisme.
Anda bisa memulai setiap struct dengan pointer ke tabel fungsi virtualnya. Ini akan menjadi masalah kinerja (setiap struct setidaknya berukuran sebuah pointer), tetapi itu bisa dilakukan. Ini akan memungkinkan fungsi virtual.
Bagaimana dengan menambahkan bidang?
Nah, saat Anda mengalokasikan struct di stack, Anda mengalokasikan sejumlah ruang. Ruang yang diperlukan ditentukan pada waktu kompilasi (baik sebelumnya atau saat JITting). Jika Anda menambahkan bidang dan kemudian menetapkan ke tipe dasar:
Ini akan menimpa beberapa bagian tumpukan yang tidak diketahui.
Alternatifnya adalah runtime untuk mencegah hal ini dengan hanya menulis ukuran byte (A) ke variabel A.
Apa yang terjadi jika B mengganti metode di A dan mereferensikan bidang Integer2? Entah runtime akan menampilkan MemberAccessException, atau metode tersebut mengakses beberapa data acak di stack. Tak satu pun dari ini diizinkan.
Sangat aman untuk memiliki pewarisan struct, selama Anda tidak menggunakan struct secara polimorfis, atau selama Anda tidak menambahkan bidang saat mewarisi. Tapi ini tidak terlalu berguna.
sumber
Ini sepertinya pertanyaan yang sangat sering. Saya merasa ingin menambahkan bahwa tipe nilai disimpan "di tempat" di mana Anda mendeklarasikan variabel; Selain detail implementasi, ini berarti tidak ada header objek yang mengatakan sesuatu tentang objek, hanya variabel yang mengetahui jenis data apa yang ada di sana.
sumber
Struktur mendukung antarmuka, jadi Anda dapat melakukan beberapa hal polimorfik dengan cara itu.
sumber
IL adalah bahasa berbasis tumpukan, jadi memanggil metode dengan argumen berjalan seperti ini:
Ketika metode ini dijalankan, ia mengeluarkan beberapa byte dari tumpukan untuk mendapatkan argumennya. Ia tahu persis berapa banyak byte yang akan muncul karena argumennya adalah penunjuk tipe referensi (selalu 4 byte pada 32-bit) atau itu adalah tipe nilai yang ukurannya selalu diketahui dengan tepat.
Jika ini adalah penunjuk tipe referensi maka metode akan mencari objek di heap dan mendapatkan penangan tipenya, yang menunjuk ke tabel metode yang menangani metode tertentu untuk tipe yang tepat tersebut. Jika ini adalah tipe nilai, maka pencarian ke tabel metode tidak diperlukan karena tipe nilai tidak mendukung pewarisan, jadi hanya ada satu kombinasi metode / tipe yang mungkin.
Jika tipe nilai mendukung pewarisan maka akan ada biaya tambahan dalam tipe tertentu dari struct harus ditempatkan di tumpukan serta nilainya, yang berarti semacam pencarian tabel metode untuk contoh konkret tertentu dari tipe tersebut. Ini akan menghilangkan keunggulan kecepatan dan efisiensi jenis nilai.
sumber