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:
Pertimbangkan mendefinisikan struktur alih-alih kelas jika instance tipe kecil dan umumnya berumur pendek atau umumnya tertanam dalam objek lain.
Jangan mendefinisikan struktur kecuali tipe memiliki semua karakteristik berikut:
- Secara logis mewakili nilai tunggal, mirip dengan tipe primitif (integer, dobel, dan sebagainya).
- Ini memiliki ukuran instance lebih kecil dari 16 byte.
- Itu tidak berubah.
- Itu tidak harus sering kotak.
Microsoft secara konsisten melanggar aturan itu
Baiklah, # 2 dan # 3. Kamus kesayangan kami memiliki 2 struct internal:
[StructLayout(LayoutKind.Sequential)] // default for structs
private struct Entry //<Tkey, TValue>
{
// View code at *Reference Source
}
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator :
IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable,
IDictionaryEnumerator, IEnumerator
{
// View code at *Reference Source
}
* 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:
- Setiap struct,
Entry
dan Enumerator
, mewakili nilai tunggal.
- Kecepatan
Entry
tidak pernah dilewatkan sebagai parameter di luar kelas Kamus. Penyelidikan lebih lanjut menunjukkan bahwa untuk memenuhi implementasi IEnumerable, Kamus menggunakan Enumerator
struct yang disalin setiap kali seorang enumerator diminta ... masuk akal.
- Internal ke kelas Kamus.
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 referensi IEnumerator
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:
- Tidak ada dalam struct di atas yang dinyatakan
readonly
- tidak dapat diubah
- Ukuran struct ini bisa lebih dari 16 byte
Entry
memiliki seumur hidup belum ditentukan (dari Add()
, untuk Remove()
, 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: 312874
MemSize: 2660827 bytes
Selesai Mengubah Ukuran: 5ms
Total waktu untuk mengisi: 889ms
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 ms
Jadi, sekarang, bagaimana jika Entry
kelas? Apakah waktu atau metrik ini benar-benar berbeda jauh?
Kapasitas: 312874
MemSize: 2660827 bytes
Selesai Mengubah Ukuran: 26ms
Total waktu untuk mengisi: 964ms
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 menggunakan Entry
sebagai tipe referensi, saya harus memasukkan kode berikut:
/*
* Added to satisfy initialization of entry elements --
* this is where the extra time is spent resizing the Entry array
* **/
for (int i = 0 ; i < prime ; i++)
{
destinationArray[i] = new Entry( );
}
/* *********************************************** */
Alasan saya harus menginisialisasi setiap elemen array Entry
sebagai tipe referensi dapat ditemukan di MSDN: Structure Design . Pendeknya:
Jangan berikan konstruktor default untuk struktur.
Jika struktur mendefinisikan konstruktor default, ketika array struktur dibuat, runtime bahasa umum secara otomatis mengeksekusi konstruktor default pada setiap elemen array.
Beberapa kompiler, seperti kompiler C #, tidak mengizinkan struktur memiliki konstruktor default.
Ini sebenarnya cukup sederhana dan kami akan meminjam dari Tiga Hukum Robotika Asimov :
- Struct harus aman digunakan
- Struct harus menjalankan fungsinya secara efisien, kecuali jika ini akan melanggar aturan # 1
- Struct harus tetap utuh selama penggunaannya kecuali kehancurannya diperlukan untuk memenuhi aturan # 1
... 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).
System.Drawing.Rectangle
melanggar ketiga aturan ini.