Kapan Anda harus menggunakan struct dan bukan kelas di C #? Model konseptual saya adalah bahwa struct digunakan pada saat-saat ketika item tersebut hanyalah kumpulan tipe nilai . Suatu cara untuk secara logis mengikat mereka semua menjadi satu kesatuan yang kohesif.
Saya menemukan aturan-aturan ini di sini :
- Sebuah struct harus mewakili nilai tunggal.
- Sebuah struct harus memiliki jejak memori kurang dari 16 byte.
- Sebaiknya struct tidak diubah setelah pembuatan.
Apakah aturan ini berfungsi? Apa arti struct secara semantik?
System.Drawing.Rectangle
melanggar ketiga aturan ini.Jawaban:
Sumber yang dirujuk oleh OP memiliki kredibilitas ... tetapi bagaimana dengan Microsoft - bagaimana pendirian penggunaan struct? Saya mencari beberapa pembelajaran tambahan dari Microsoft , dan inilah yang saya temukan:
Microsoft secara konsisten melanggar aturan itu
Baiklah, # 2 dan # 3. Kamus kesayangan kami memiliki 2 struct internal:
* Sumber Referensi
Sumber 'JonnyCantCode.com' mendapat 3 dari 4 - cukup dimaafkan karena # 4 mungkin tidak akan menjadi masalah. Jika Anda menemukan tinju struct, pikirkan kembali arsitektur Anda.
Mari kita lihat mengapa Microsoft akan menggunakan struct ini:
Entry
danEnumerator
, mewakili nilai tunggal.Entry
tidak pernah dilewatkan sebagai parameter di luar kelas Kamus. Penyelidikan lebih lanjut menunjukkan bahwa untuk memenuhi implementasi IEnumerable, Kamus menggunakanEnumerator
struct yang disalin setiap kali seorang enumerator diminta ... masuk akal.Enumerator
bersifat publik karena Kamus enumerable dan harus memiliki aksesibilitas yang sama ke implementasi antarmuka IEnumerator - mis. pengambil IEnumerator.Pembaruan - Selain itu, sadari bahwa ketika struct mengimplementasikan antarmuka - seperti yang dilakukan Enumerator - dan dilemparkan ke tipe yang diimplementasikan, struct menjadi tipe referensi dan dipindahkan ke heap. Internal untuk kelas Dictionary, Enumerator adalah masih jenis nilai. Namun, begitu metode memanggil
GetEnumerator()
, tipe referensiIEnumerator
dikembalikan.Apa yang tidak kita lihat di sini adalah upaya atau bukti persyaratan untuk menjaga struct tetap atau mempertahankan ukuran instance hanya 16 byte atau kurang:
readonly
- tidak dapat diubahEntry
memiliki seumur hidup belum ditentukan (dariAdd()
, untukRemove()
,Clear()
atau pengumpulan sampah);Dan ... 4. Kedua struct menyimpan TKey dan TValue, yang kita semua tahu cukup mampu menjadi tipe referensi (info bonus tambahan)
Terlepas dari kunci-kunci yang dihancurkan, kamus-kamus lebih cepat sebagian karena memunculkan struct lebih cepat daripada tipe referensi. Di sini, saya punya
Dictionary<int, int>
yang menyimpan 300.000 bilangan bulat acak dengan kunci yang bertambah secara berurutan.Kapasitas : jumlah elemen yang tersedia sebelum array internal harus diubah ukurannya.
MemSize : ditentukan dengan membuat serial kamus ke dalam MemoryStream dan mendapatkan panjang byte (cukup akurat untuk tujuan kita).
Resize Selesai : waktu yang diperlukan untuk mengubah ukuran larik internal dari 150862 elemen menjadi 312874 elemen. Ketika Anda mengetahui bahwa setiap elemen disalin secara berurutan
Array.CopyTo()
, itu tidak terlalu buruk.Total waktu untuk mengisi : diakui miring karena penebangan dan
OnResize
acara yang saya tambahkan ke sumber; Namun, masih mengesankan untuk mengisi 300k bilangan bulat sambil mengubah ukuran 15 kali selama operasi. Hanya karena penasaran, berapa total waktu yang harus diisi jika saya sudah tahu kapasitasnya? 13 msJadi, sekarang, bagaimana jika
Entry
kelas? Apakah waktu atau metrik ini benar-benar berbeda jauh?Jelas, perbedaan besar adalah dalam mengubah ukuran. Adakah perbedaan jika Kamus diinisialisasi dengan Kapasitas? Tidak cukup untuk peduli dengan ... 12ms .
Yang terjadi adalah, karena
Entry
merupakan struct, itu tidak memerlukan inisialisasi seperti tipe referensi. Ini adalah keindahan dan kutukan dari tipe nilai. Untuk menggunakanEntry
sebagai tipe referensi, saya harus memasukkan kode berikut:Alasan saya harus menginisialisasi setiap elemen array
Entry
sebagai tipe referensi dapat ditemukan di MSDN: Structure Design . Pendeknya:Ini sebenarnya cukup sederhana dan kami akan meminjam dari Tiga Hukum Robotika Asimov :
... apa yang kita ambil dari ini : singkatnya, bertanggung jawab dengan penggunaan tipe nilai. Mereka cepat dan efisien, tetapi memiliki kemampuan untuk menyebabkan banyak perilaku tak terduga jika tidak dikelola dengan baik (yaitu salinan yang tidak disengaja).
sumber
Decimal
atauDateTime
], maka jika tidak mau mematuhi tiga aturan lainnya, itu harus digantikan oleh kelas. Jika suatu struktur menyimpan kumpulan variabel yang tetap, yang masing-masingnya dapat memiliki nilai apa pun yang akan valid untuk jenisnya [misalnyaRectangle
], maka ia harus mematuhi aturan yang berbeda , beberapa di antaranya bertentangan dengan yang untuk struktur "nilai tunggal" .Dictionary
tipe entri dengan alasan itu hanya tipe internal, kinerja dianggap lebih penting daripada semantik, atau alasan lainnya. Maksud saya adalah bahwa jenis sepertiRectangle
harus memiliki kontennya diekspos sebagai bidang yang dapat diedit secara individual bukan "karena" manfaat kinerja lebih besar daripada ketidaksempurnaan semantik yang dihasilkan, tetapi karena jenis tersebut secara semantik mewakili sekumpulan nilai independen yang tetap , dan karenanya struktur yang dapat diubah adalah keduanya lebih berkinerja dan secara semantik unggul .Kapanpun Anda:
Namun, peringatannya adalah bahwa struct (sewenang-wenang besar) lebih mahal untuk dilewatkan daripada referensi kelas (biasanya satu kata mesin), sehingga kelas bisa berakhir lebih cepat dalam praktik.
sumber
(Guid)null
(tidak apa-apa untuk memberikan null ke tipe referensi), di antara hal-hal lain.Saya tidak setuju dengan aturan yang diberikan di pos asli. Ini aturan saya:
1) Anda menggunakan struct untuk kinerja ketika disimpan dalam array. (lihat juga Kapan struct jawabannya? )
2) Anda membutuhkannya dalam kode yang meneruskan data terstruktur ke / dari C / C ++
3) Jangan menggunakan struct kecuali Anda membutuhkannya:
sumber
struct
untuk mengetahui bagaimana ia akan berperilaku, tetapi jika ada sesuatustruct
dengan bidang yang terbuka, itu saja yang harus diketahui. Jika suatu objek mengekspos properti dari tipe bidang bidang-terbuka, dan jika kode membaca struct itu ke variabel dan memodifikasi, seseorang dapat dengan aman memprediksi bahwa tindakan seperti itu tidak akan mempengaruhi objek yang propertinya dibaca kecuali atau sampai struct ditulis kembali. Sebaliknya, jika properti adalah tipe kelas yang bisa berubah, membacanya dan memodifikasinya mungkin memperbarui objek yang mendasarinya seperti yang diharapkan, tapi ...Gunakan struct ketika Anda ingin semantik nilai sebagai lawan dari semantik referensi.
Edit
Tidak yakin mengapa orang downvoting ini, tetapi ini adalah poin yang valid, dan dibuat sebelum op menjelaskan pertanyaannya, dan itu adalah alasan dasar yang paling mendasar untuk sebuah struct.
Jika Anda memerlukan semantik referensi, Anda perlu kelas bukan struct.
sumber
Selain jawaban "itu adalah nilai", satu skenario khusus untuk menggunakan struct adalah ketika Anda tahu bahwa Anda memiliki serangkaian data yang menyebabkan masalah pengumpulan sampah, dan Anda memiliki banyak objek. Misalnya, daftar besar / array instance Person. Metafora alami di sini adalah kelas, tetapi jika Anda memiliki jumlah instance Orang yang berumur panjang, mereka dapat menyumbat GEN-2 dan menyebabkan kios-kios GC. Jika skenario menjaminnya, salah satu pendekatan potensial di sini adalah dengan menggunakan array (bukan daftar) Orang struct , yaitu
Person[]
. Sekarang, alih-alih memiliki jutaan objek di GEN-2, Anda memiliki sepotong tunggal pada LOH (saya mengasumsikan tidak ada string dll di sini - yaitu nilai murni tanpa referensi). Ini memiliki dampak GC yang sangat kecil.Bekerja dengan data ini aneh, karena data mungkin terlalu besar untuk sebuah struct, dan Anda tidak ingin menyalin nilai lemak sepanjang waktu. Namun, mengaksesnya secara langsung dalam array tidak menyalin struct - itu di tempat (kontras dengan pengindeks daftar, yang tidak menyalin). Ini berarti banyak pekerjaan dengan indeks:
Perhatikan bahwa menjaga nilai-nilai itu sendiri tidak berubah akan membantu di sini. Untuk logika yang lebih kompleks, gunakan metode dengan parameter by-ref:
Sekali lagi, ini sudah di tempat - kami belum menyalin nilainya.
Dalam skenario yang sangat spesifik, taktik ini bisa sangat berhasil; Namun, itu adalah scernario yang cukup canggih yang harus dicoba hanya jika Anda tahu apa yang Anda lakukan dan mengapa. Default di sini adalah kelas.
sumber
List
Saya percaya, menggunakanArray
layar belakang. tidak ?Dari spesifikasi Bahasa C # :
Alternatif lain adalah menjadikan Point sebagai struct.
Sekarang, hanya satu objek yang dipakai - satu untuk array - dan instance Point disimpan in-line dalam array.
Konstruktor struktur dipanggil dengan operator baru, tetapi itu tidak menyiratkan bahwa memori sedang dialokasikan. Alih-alih secara dinamis mengalokasikan objek dan mengembalikan referensi ke sana, konstruktor struct hanya mengembalikan nilai struct itu sendiri (biasanya di lokasi sementara di stack), dan nilai ini kemudian disalin seperlunya.
Dengan kelas, dimungkinkan untuk dua variabel untuk referensi objek yang sama dan dengan demikian mungkin untuk operasi pada satu variabel untuk mempengaruhi objek yang dirujuk oleh variabel lain. Dengan struct, masing-masing variabel memiliki salinan data mereka sendiri, dan tidak mungkin operasi yang satu mempengaruhi yang lain. Sebagai contoh, output yang dihasilkan oleh fragmen kode berikut ini tergantung pada apakah Point adalah kelas atau struct.
Jika Point adalah kelas, outputnya adalah 20 karena a dan b mereferensikan objek yang sama. Jika Point adalah struct, outputnya adalah 10 karena penugasan a ke b menciptakan salinan nilai, dan salinan ini tidak terpengaruh oleh penugasan selanjutnya ke ax
Contoh sebelumnya menyoroti dua keterbatasan struct. Pertama, menyalin seluruh struct biasanya kurang efisien daripada menyalin referensi objek, sehingga penugasan dan nilai parameter yang lewat bisa lebih mahal dengan struct daripada dengan tipe referensi. Kedua, kecuali untuk parameter ref dan out, tidak mungkin membuat referensi untuk struct, yang mengesampingkan penggunaannya dalam sejumlah situasi.
sumber
ref
ke struktur yang bisa berubah dan tahu bahwa setiap mutasi yang dilakukan metode luar akan dilakukan sebelum kembali. Sayang sekali .net tidak memiliki konsep parameter fana dan nilai fungsi pengembalian, karena ...ref
dicapai dengan objek kelas. Pada dasarnya, variabel lokal, parameter, dan nilai-nilai fungsi kembali bisa bertahan (default), dikembalikan, atau sesaat. Kode akan dilarang menyalin hal-hal fana ke apa pun yang akan hidup lebih lama dari ruang lingkup saat ini. Hal-hal yang dapat dikembalikan akan seperti hal-hal yang fana kecuali bahwa mereka dapat dikembalikan dari suatu fungsi. Nilai pengembalian suatu fungsi akan terikat oleh batasan ketat yang berlaku pada parameter "yang dapat dikembalikan".Structs baik untuk representasi data atom, di mana data tersebut dapat disalin beberapa kali oleh kode. Mengkloning suatu objek secara umum lebih mahal daripada menyalin struct, karena melibatkan mengalokasikan memori, menjalankan konstruktor dan deallocating / pengumpulan sampah ketika dilakukan dengannya.
sumber
Ini aturan dasar.
Jika semua bidang anggota adalah tipe nilai buat sebuah struct .
Jika salah satu bidang anggota adalah tipe referensi, buat kelas . Ini karena bidang tipe referensi memerlukan alokasi heap.
Exmaples
sumber
string
secara semantik setara dengan nilai-nilai, dan menyimpan referensi ke objek yang tidak dapat diubah ke dalam suatu bidang tidak memerlukan alokasi heap. Perbedaan antara struct dengan bidang publik terbuka dan objek kelas dengan bidang publik terbuka adalah bahwa diberi urutan kodevar q=p; p.X=4; q.X=5;
,p.X
akan memiliki nilai 4 jikaa
merupakan tipe struktur, dan 5 jika itu adalah tipe kelas. Jika seseorang ingin dapat dengan mudah memodifikasi anggota tipe, seseorang harus memilih 'kelas' atau 'struct' berdasarkan apakah seseorang ingin perubahanq
mempengaruhip
.ArraySegment<T>
merangkum aT[]
, yang selalu merupakan tipe kelas. Tipe strukturKeyValuePair<TKey,TValue>
sering digunakan dengan tipe kelas sebagai parameter generik.Pertama: Skenario interop atau ketika Anda perlu menentukan tata letak memori
Kedua: Ketika data hampir sama ukurannya dengan pointer referensi pula.
sumber
Anda perlu menggunakan "struct" dalam situasi di mana Anda ingin secara eksplisit menentukan tata letak memori menggunakan StructLayoutAttribute - biasanya untuk PInvoke.
Sunting: Komentar menunjukkan bahwa Anda dapat menggunakan kelas atau struct dengan StructLayoutAttribute dan itu memang benar. Dalam praktiknya, Anda biasanya akan menggunakan struct - itu dialokasikan pada tumpukan vs tumpukan yang masuk akal jika Anda hanya meneruskan argumen ke pemanggilan metode yang tidak dikelola.
sumber
Saya menggunakan struct untuk mengemas atau membongkar segala bentuk format komunikasi biner. Itu termasuk membaca atau menulis ke disk, daftar vertex DirectX, protokol jaringan, atau berurusan dengan data terenkripsi / terkompresi.
Tiga pedoman yang Anda daftarkan belum berguna bagi saya dalam konteks ini. Ketika saya perlu menulis empat ratus byte barang dalam Urutan Tertentu, saya akan mendefinisikan struct empat ratus byte, dan saya akan mengisinya dengan nilai apa pun yang tidak terkait yang seharusnya dimiliki, dan saya akan untuk mengaturnya dengan cara apa pun yang paling masuk akal saat itu. (Oke, empat ratus byte akan sangat aneh - tetapi ketika saya sedang menulis file Excel untuk mencari nafkah, saya berurusan dengan struct hingga sekitar empat puluh byte di seluruh, karena itulah seberapa besar beberapa catatan BIFF ADALAH.)
sumber
Dengan pengecualian pada valuetypes yang digunakan secara langsung oleh runtime dan berbagai lainnya untuk keperluan PInvoke, Anda hanya boleh menggunakan valuetypes dalam 2 skenario.
sumber
this
parameter sementara yang digunakan untuk memanggil metode-metodenya); kelas memungkinkan seseorang untuk menggandakan referensi..NET mendukung
value types
danreference types
(di Jawa, Anda hanya dapat menentukan jenis referensi). Contohreference types
dialokasikan di tumpukan dikelola dan sampah dikumpulkan ketika tidak ada referensi yang beredar untuk mereka. Contohvalue types
, di sisi lain, dialokasikan dalamstack
, dan karenanya memori yang dialokasikan direklamasi segera setelah ruang lingkup mereka berakhir. Dan tentu saja,value types
dilewati oleh nilai, danreference types
dengan referensi. Semua tipe data primitif C #, kecuali untuk System.String, adalah tipe nilai.Kapan harus menggunakan struct di atas kelas,
Di C #,
structs
adalahvalue types
, kelas adalahreference types
. Anda dapat membuat tipe nilai, dalam C #, menggunakanenum
kata kunci danstruct
kata kunci. Menggunakanvalue type
bukanreference type
akan menghasilkan lebih sedikit objek pada heap yang dikelola, yang menghasilkan lebih sedikit beban pada pengumpul sampah, lebih jarang siklus GC, dan akibatnya kinerja yang lebih baik. Namun,value types
memiliki kelemahan mereka juga. Melewati yang besarstruct
pasti lebih mahal daripada melewati referensi, itu salah satu masalah yang jelas. Masalah lainnya adalah overhead yang terkait dengannyaboxing/unboxing
. Jika Anda bertanya-tanya apaboxing/unboxing
artinya, ikuti tautan ini untuk penjelasanboxing
danunboxing
. Terlepas dari kinerja, ada kalanya Anda hanya perlu tipe untuk memiliki semantik nilai, yang akan sangat sulit (atau jelek) untuk diimplementasikan jikareference types
hanya itu yang Anda miliki. Andavalue types
hanya harus menggunakan , Ketika Anda perlu menyalin semantik atau membutuhkan inisialisasi otomatis, biasanya dalamarrays
jenis ini.sumber
ref
. Melewati setiap struktur ukuran denganref
biaya sama seperti melewati referensi kelas dengan nilai. Menyalin semua struktur ukuran atau meneruskan dengan nilai lebih murah daripada melakukan salinan defensif dari objek kelas dan menyimpan atau melewati referensi untuk itu. Kelas waktu besar lebih baik daripada struct untuk menyimpan nilai adalah (1) ketika kelas tidak dapat diubah (sehingga untuk menghindari menyalin defensif), dan setiap contoh yang dibuat akan dilewatkan banyak, atau ...readOnlyStruct.someMember = 5;
adalah tidak membuatsomeMember
properti hanya baca, tetapi menjadikannya bidang.Sebuah struct adalah jenis nilai. Jika Anda menetapkan struct ke variabel baru, variabel baru akan berisi salinan aslinya.
Eksekusi hasil berikut dalam 5 contoh struct yang disimpan dalam memori:
Sebuah kelas adalah tipe referensi. Saat Anda menetapkan kelas ke variabel baru, variabel tersebut berisi referensi ke objek kelas asli.
Eksekusi hasil berikut ini hanya dalam satu contoh objek kelas dalam memori.
Struct s dapat meningkatkan kemungkinan kesalahan kode. Jika objek nilai diperlakukan seperti objek referensi yang bisa berubah, pengembang mungkin akan terkejut ketika perubahan yang dilakukan tiba-tiba hilang.
sumber
Saya membuat tolok ukur kecil dengan BenchmarkDotNet untuk mendapatkan pemahaman yang lebih baik tentang manfaat "struct" dalam angka. Saya sedang menguji pengulangan melalui array (atau daftar) struct (atau kelas). Membuat susunan atau daftar tersebut di luar jangkauan tolok ukur - jelas bahwa "kelas" yang lebih berat akan menggunakan lebih banyak memori, dan akan melibatkan GC.
Jadi kesimpulannya adalah: berhati-hatilah dengan LINQ dan hidden structs boxing / unboxing dan gunakan structs untuk optimasi mikro secara ketat tetap dengan array.
PS Patokan lain tentang melewati struct / kelas melalui tumpukan panggilan ada https://stackoverflow.com/a/47864451/506147
Kode:
sumber
Tipe struktur dalam C # atau bahasa .net lainnya umumnya digunakan untuk menampung hal-hal yang seharusnya berperilaku seperti kelompok nilai berukuran tetap. Aspek yang berguna dari tipe struktur adalah bahwa bidang instance tipe struktur dapat dimodifikasi dengan mengubah lokasi penyimpanan di mana ia disimpan, dan tidak dengan cara lain. Dimungkinkan untuk mengkodekan struktur sedemikian rupa sehingga satu-satunya cara untuk bermutasi bidang apa pun adalah dengan membangun contoh baru dan kemudian menggunakan penugasan struct untuk memutasi semua bidang target dengan menimpa mereka dengan nilai-nilai dari instance baru, tetapi kecuali sebuah struct tidak menyediakan sarana untuk membuat instance di mana bidangnya memiliki nilai-nilai non-default, semua bidangnya akan bisa berubah jika dan jika struct itu sendiri disimpan di lokasi yang bisa diubah.
Perhatikan bahwa mungkin untuk merancang tipe struktur sehingga pada dasarnya akan berperilaku seperti tipe kelas, jika struktur berisi bidang tipe kelas pribadi, dan mengarahkan anggotanya sendiri ke objek kelas yang dibungkus. Sebagai contoh, sebuah
PersonCollection
mungkin menawarkan propertiSortedByName
danSortedById
, keduanya memiliki referensi "tidak berubah" kePersonCollection
(diatur dalam konstruktor mereka) dan mengimplementasikannyaGetEnumerator
dengan memanggil salah satucreator.GetNameSortedEnumerator
ataucreator.GetIdSortedEnumerator
. Struct tersebut akan berperilaku seperti referensi ke aPersonCollection
, kecuali bahwaGetEnumerator
metode mereka akan terikat pada metode yang berbeda diPersonCollection
. Seseorang juga dapat memiliki struktur yang membungkus suatu bagian dari array (misalnya seseorang dapat mendefinisikan suatuArrayRange<T>
struktur yang akan menahan suatu yangT[]
disebutArr
, suatu intOffset
, dan suatu intLength
, dengan properti yang diindeks yang, untuk indeksidx
dalam kisaran 0 hinggaLength-1
, akan mengaksesArr[idx+Offset]
). Sayangnya, jikafoo
ini adalah contoh read-only dari struktur seperti itu, versi kompiler saat ini tidak akan memungkinkan operasi sepertifoo[3]+=4;
karena mereka tidak memiliki cara untuk menentukan apakah operasi tersebut akan mencoba menulis ke bidangfoo
.Dimungkinkan juga untuk mendesain struktur untuk berperilaku seperti tipe nilai yang menyimpan koleksi berukuran variabel (yang akan tampak disalin setiap kali struct) tetapi satu-satunya cara untuk membuat pekerjaan itu adalah untuk memastikan bahwa tidak ada objek yang mana struct memegang referensi yang akan pernah terpapar pada apa pun yang mungkin bermutasi. Sebagai contoh, seseorang dapat memiliki struct mirip array yang menampung array privat, dan metode "put" yang diindeks membuat array baru yang isinya seperti aslinya kecuali untuk satu elemen yang diubah. Sayangnya, bisa agak sulit untuk membuat struct seperti itu bekerja secara efisien. Meskipun ada saat-saat semantik struct dapat menjadi nyaman (misalnya bisa meneruskan koleksi seperti array ke rutin, dengan penelepon dan callee keduanya mengetahui bahwa kode luar tidak akan mengubah koleksi,
sumber
Nah - Saya tidak sepenuhnya setuju dengan aturan. Mereka adalah pedoman yang baik untuk dipertimbangkan dengan kinerja dan standardisasi, tetapi tidak mengingat kemungkinan.
Seperti yang dapat Anda lihat di respons, ada banyak cara kreatif untuk menggunakannya. Jadi, pedoman ini perlu seperti itu, selalu demi kinerja dan efisiensi.
Dalam hal ini, saya menggunakan kelas untuk mewakili objek dunia nyata dalam bentuk yang lebih besar, saya menggunakan struct untuk mewakili objek yang lebih kecil yang memiliki penggunaan yang lebih tepat. Cara Anda mengatakannya, "keseluruhan yang lebih kohesif." Kata kunci menjadi kohesif. Kelas akan lebih banyak elemen berorientasi objek, sementara struct dapat memiliki beberapa karakteristik tersebut, meskipun pada skala yang lebih kecil. IMO.
Saya sering menggunakannya di tag Treeview dan Listview di mana atribut statis umum dapat diakses dengan sangat cepat. Saya selalu berjuang untuk mendapatkan info ini dengan cara lain. Sebagai contoh, dalam aplikasi database saya, saya menggunakan Treeview di mana saya memiliki Tabel, SP, Fungsi, atau objek lainnya. Saya membuat dan mengisi struct saya, memasukkannya ke dalam tag, menariknya keluar, mendapatkan data pilihan dan sebagainya. Saya tidak akan melakukan ini dengan kelas!
Saya mencoba dan menjaga mereka tetap kecil, menggunakannya dalam situasi instan, dan menjaga mereka agar tidak berubah. Adalah bijaksana untuk menyadari ingatan, alokasi, dan kinerja. Dan pengujian sangat diperlukan.
sumber
double
nilai untuk koordinat tersebut, spesifikasi semacam itu akan memaksa untuk berperilaku semantik identik dengan struct bidang-terbuka kecuali untuk beberapa detail perilaku multi-threaded (kelas abadi akan lebih baik dalam beberapa kasus, sementara struct bidang-terbuka akan lebih baik dalam kasus lain; yang disebut "immutable" struct akan lebih buruk dalam setiap kasus).Aturan saya adalah
1, Selalu gunakan kelas;
2, Jika ada masalah kinerja, saya mencoba untuk mengubah beberapa kelas ke struct tergantung pada aturan yang disebutkan @ Ekstrak, dan kemudian melakukan tes untuk melihat apakah perubahan ini dapat meningkatkan kinerja.
sumber
Foo
untuk merangkum koleksi tetap dari nilai-nilai independen (misalnya koordinat suatu titik) yang terkadang ingin diedarkan sebagai grup dan terkadang ingin diubah secara independen. Saya tidak menemukan pola apa pun untuk menggunakan kelas yang menggabungkan kedua tujuan hampir sama baiknya dengan struct bidang terbuka sederhana (yang, sebagai koleksi tetap variabel independen, sangat cocok dengan tagihan).public readonly
bidang dalam tipe saya juga, karena membuat properti hanya-baca terlalu banyak bekerja untuk praktis tidak ada manfaatnya.)MyListOfPoint[3].Offset(2,3);
menjadivar temp=MyListOfPoint[3]; temp.Offset(2,3);
, sebuah transformasi yang palsu ketika diterapkan ...Offset
metode. Cara yang tepat untuk mencegah kode palsu seperti itu tidak boleh membuat struct tidak dapat diubah selamanya, tetapi sebagai gantinya membiarkan metode sukaOffset
ditandai dengan atribut yang melarang transformasi yang disebutkan di atas. Konversi numerik implisit juga bisa jauh lebih baik jika mereka dapat ditandai sehingga hanya berlaku dalam kasus-kasus di mana doa mereka akan jelas. Jika ada kelebihan untukfoo(float,float)
danfoo(double,double)
, saya berpendapat bahwa mencoba menggunakanfloat
dandouble
sering tidak boleh menerapkan konversi implisit, tetapi seharusnya menjadi kesalahan.double
nilai kefloat
, atau meneruskannya ke metode yang dapat mengambilfloat
argumen tetapi tidakdouble
, hampir selalu melakukan apa yang diinginkan oleh pemrogram. Sebaliknya, menetapkanfloat
ekspresidouble
tanpa typecast eksplisit seringkali merupakan kesalahan. Satu-satunya waktu yang memungkinkandouble->float
konversi implisit akan menyebabkan masalah adalah ketika itu akan menyebabkan kelebihan yang kurang ideal untuk dipilih. Saya berpendapat bahwa cara yang tepat untuk mencegah hal itu seharusnya tidak melarang float implisit ganda>, tetapi menandai kelebihan beban dengan atribut untuk melarang konversi.Kelas adalah tipe referensi. Ketika objek kelas dibuat, variabel yang objek ditugaskan hanya memiliki referensi ke memori itu. Ketika referensi objek ditugaskan ke variabel baru, variabel baru merujuk ke objek asli. Perubahan yang dilakukan melalui satu variabel tercermin dalam variabel lainnya karena keduanya merujuk pada data yang sama. Str adalah tipe nilai. Ketika sebuah struct dibuat, variabel yang diberikan struct menyimpan data aktual struct. Ketika struct ditugaskan ke variabel baru, itu akan disalin. Variabel baru dan variabel asli karenanya mengandung dua salinan terpisah dari data yang sama. Perubahan yang dilakukan pada satu salinan tidak memengaruhi salinan lainnya. Secara umum, kelas digunakan untuk memodelkan perilaku yang lebih kompleks, atau data yang dimaksudkan untuk dimodifikasi setelah objek kelas dibuat.
Kelas dan Struktur (Panduan Pemrograman C #)
sumber
MITOS # 1: STRUCTS ADALAH KELAS LIGHTWEIGHT
Mitos ini muncul dalam berbagai bentuk. Beberapa orang percaya bahwa tipe nilai tidak dapat atau tidak seharusnya memiliki metode atau perilaku signifikan lainnya — mereka harus digunakan sebagai tipe transfer data sederhana, hanya dengan bidang publik atau properti sederhana. Tipe DateTime adalah contoh tandingan yang baik untuk ini: masuk akal jika itu menjadi tipe nilai, dalam hal menjadi unit mendasar seperti angka atau karakter, dan juga masuk akal untuk dapat melakukan perhitungan berdasarkan nilainya. Melihat hal-hal dari arah lain, tipe transfer data harus sering menjadi tipe referensi — keputusan harus didasarkan pada nilai yang diinginkan atau semantik tipe referensi, bukan kesederhanaan tipe. Orang lain percaya bahwa tipe nilai "lebih ringan" daripada tipe referensi dalam hal kinerja. Yang benar adalah bahwa dalam beberapa kasus nilai jenis lebih berkinerja - mereka tidak memerlukan pengumpulan sampah kecuali mereka kotak, tidak memiliki jenis identifikasi overhead, dan tidak memerlukan dereferencing, misalnya. Tetapi dengan cara lain, tipe referensi lebih berkinerja baik - melewati parameter, menetapkan nilai ke variabel, mengembalikan nilai, dan operasi yang serupa hanya memerlukan 4 atau 8 byte untuk dioptasi (tergantung pada apakah Anda menjalankan CLR 32-bit atau 64-bit) ) daripada menyalin semua data. Bayangkan jika ArrayList entah bagaimana merupakan tipe nilai "murni", dan meneruskan ekspresi ArrayList ke metode yang terlibat menyalin semua datanya! Dalam hampir semua kasus, kinerja sebenarnya tidak ditentukan oleh keputusan semacam ini. Kemacetan hampir tidak pernah Anda pikirkan, dan sebelum Anda membuat keputusan desain berdasarkan kinerja, Anda harus mengukur opsi yang berbeda. Perlu dicatat bahwa kombinasi dari kedua kepercayaan itu juga tidak berhasil. Tidak masalah berapa banyak metode yang dimiliki suatu tipe (apakah itu kelas atau struct) - memori yang diambil per instance tidak terpengaruh. (Ada biaya dalam hal memori yang digunakan untuk kode itu sendiri, tetapi itu terjadi sekali daripada untuk setiap contoh.)
MITOS # 2: JENIS-JENIS REFERENSI HIDUP DI HEAP; JENIS-JENIS NILAI LANGSUNG DI STACK
Yang ini sering disebabkan oleh kemalasan pada bagian orang yang mengulanginya. Bagian pertama sudah benar — sebuah instance dari tipe referensi selalu dibuat di heap. Itu bagian kedua yang menyebabkan masalah. Seperti yang sudah saya catat, nilai variabel tinggal di mana pun ia dideklarasikan, jadi jika Anda memiliki kelas dengan variabel instan dari tipe int, nilai variabel untuk objek tertentu akan selalu berada di tempat sisa data untuk objek tersebut adalah— di atas tumpukan. Hanya variabel lokal (variabel yang dideklarasikan dalam metode) dan parameter metode yang hidup di stack. Dalam C # 2 dan yang lebih baru, bahkan beberapa variabel lokal tidak benar-benar hidup di stack, seperti yang akan Anda lihat ketika kita melihat metode anonim di bab 5. APAKAH KONSEP INI RELEVAN SEKARANG? Dapat diperdebatkan bahwa jika Anda menulis kode terkelola, Anda harus membiarkan runtime khawatir tentang bagaimana memori digunakan. Memang, spesifikasi bahasa tidak memberikan jaminan tentang apa yang tinggal di mana; runtime di masa depan mungkin dapat membuat beberapa objek pada stack jika ia tahu ia dapat lolos, atau kompiler C # dapat menghasilkan kode yang hampir tidak menggunakan stack sama sekali. Mitos selanjutnya biasanya hanya masalah terminologi.
MITOS # 3: OBYEK BERLALU OLEH REFERENSI DALAM C # OLEH DEFAULT
Ini mungkin mitos yang paling banyak diperbanyak. Sekali lagi, orang-orang yang membuat klaim ini sering (walaupun tidak selalu) tahu bagaimana sebenarnya C # berperilaku, tetapi mereka tidak tahu apa arti sebenarnya "lewat referensi". Sayangnya, ini membingungkan bagi orang yang tahu apa artinya. Definisi formal pass by reference relatif rumit, melibatkan nilai-l dan terminologi ilmu komputer yang serupa, tetapi yang penting adalah bahwa jika Anda melewatkan variabel dengan referensi, metode yang Anda panggil dapat mengubah nilai variabel pemanggil dengan mengubah nilai parameternya. Sekarang, ingat bahwa nilai variabel tipe referensi adalah referensi, bukan objek itu sendiri. Anda dapat mengubah konten objek yang dirujuk oleh parameter tanpa parameter itu sendiri dilewatkan oleh referensi. Contohnya,
Ketika metode ini dipanggil, nilai parameter (referensi ke StringBuilder) diteruskan oleh nilai. Jika Anda mengubah nilai variabel builder dalam metode — misalnya, dengan statement builder = null; —bahwa perubahan tidak akan terlihat oleh penelepon, bertentangan dengan mitos. Sangat menarik untuk dicatat bahwa tidak hanya bit "dengan referensi" mitos yang tidak akurat, tetapi juga bit "objek yang dilewati". Objek itu sendiri tidak pernah dilewatkan, baik dengan referensi atau dengan nilai. Ketika jenis referensi terlibat, variabel dilewatkan oleh referensi atau nilai argumen (referensi) dilewatkan oleh nilai. Selain dari hal lain, ini menjawab pertanyaan tentang apa yang terjadi ketika null digunakan sebagai argumen nilai-jika-objek dilewatkan, itu akan menyebabkan masalah, karena tidak akan ada objek untuk dilewati! Sebagai gantinya, referensi nol dilewatkan oleh nilai dengan cara yang sama seperti referensi lainnya. Jika penjelasan singkat ini membuat Anda bingung, Anda mungkin ingin melihat artikel saya, "Parameter meneruskan C #," (http://mng.bz/otVt ), yang lebih detail. Mitos-mitos ini bukan satu-satunya di sekitar. Boxing dan unboxing masuk untuk bagian kesalahpahaman mereka, yang akan saya coba jelaskan selanjutnya.
Referensi: C # in Depth 3rd Edition oleh Jon Skeet
sumber
Saya pikir pendekatan pertama yang baik adalah "tidak pernah".
Saya pikir perkiraan kedua yang baik adalah "tidak pernah".
Jika Anda putus asa untuk perf, pertimbangkan mereka, tetapi kemudian selalu mengukur.
sumber
Saya hanya berurusan dengan Windows Communication Foundation [WCF] Named Pipe dan saya perhatikan bahwa memang masuk akal untuk menggunakan Structs untuk memastikan bahwa pertukaran data adalah tipe nilai dan bukan tipe referensi .
sumber
C # struct adalah alternatif ringan untuk sebuah kelas. Ini bisa melakukan hampir sama dengan kelas, tetapi lebih murah untuk menggunakan struct daripada kelas. Alasan untuk ini sedikit teknis, tetapi untuk menyimpulkan, contoh baru dari kelas ditempatkan di heap, di mana struct yang baru dipakai ditempatkan pada stack. Selain itu, Anda tidak berurusan dengan referensi untuk struct, seperti dengan kelas, tetapi Anda bekerja secara langsung dengan instance struct. Ini juga berarti bahwa ketika Anda meneruskan struct ke suatu fungsi, itu dengan nilai, bukan sebagai referensi. Ada lebih banyak tentang ini di bab tentang parameter fungsi.
Jadi, Anda harus menggunakan struct ketika Anda ingin mewakili struktur data yang lebih sederhana, dan terutama jika Anda tahu bahwa Anda akan membuat banyak dari mereka. Ada banyak contoh dalam kerangka .NET, di mana Microsoft telah menggunakan struct bukan kelas, misalnya Point, Rectangle dan Color struct.
sumber
Struct dapat digunakan untuk meningkatkan kinerja pengumpulan sampah. Meskipun Anda biasanya tidak perlu khawatir tentang kinerja GC, ada skenario di mana itu bisa menjadi pembunuh. Seperti cache besar dalam aplikasi latensi rendah. Lihat posting ini sebagai contoh:
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
sumber
Jenis struktur atau nilai dapat digunakan dalam skenario berikut -
Anda bisa tahu lebih banyak tentang tipe nilai dan tipe nilai di sini di tautan ini
sumber
Secara singkat, gunakan struct jika:
1- properti / bidang objek Anda tidak perlu diubah. Maksud saya Anda hanya ingin memberi mereka nilai awal dan kemudian membacanya.
2- properti dan bidang dalam objek Anda adalah tipe nilai dan tidak terlalu besar.
Jika demikian, Anda dapat memanfaatkan struct untuk kinerja yang lebih baik dan alokasi memori yang dioptimalkan karena mereka hanya menggunakan tumpukan daripada tumpukan dan tumpukan (di kelas)
sumber
Saya jarang menggunakan struct untuk sesuatu. Tapi itu hanya aku. Itu tergantung apakah saya perlu objek menjadi nullable atau tidak.
Seperti yang dinyatakan dalam jawaban lain, saya menggunakan kelas untuk objek dunia nyata. Saya juga memiliki pola pikir struct yang digunakan untuk menyimpan sejumlah kecil data.
sumber
Struktur dalam banyak hal seperti kelas / objek. Struktur dapat berisi fungsi, anggota dan dapat diwarisi. Tetapi struktur dalam C # digunakan hanya untuk memegang data . Struktur memang membutuhkan RAM lebih sedikit daripada kelas dan lebih mudah untuk mengumpulkan sampah . Tetapi ketika Anda menggunakan fungsi dalam struktur Anda, maka kompiler benar-benar mengambil struktur yang sangat mirip dengan kelas / objek, jadi jika Anda menginginkan sesuatu dengan fungsi, maka gunakan kelas / objek .
sumber