Pertama, mari kita bicara tentang Jenis Referensi dan Jenis Nilai. Saya pikir orang tidak benar-benar memahami perbedaan dan manfaat keduanya. Mereka biasanya mengatakan tipe referensi menyimpan konten di heap dan tipe nilai menyimpan konten di stack, yang salah.
Mari kita bahas perbedaan nyata:
- Tipe nilai : nilainya adalah seluruh struktur . Nilai tipe referensi adalah referensi ke objek. - Struktur dalam memori: tipe nilai hanya berisi data yang Anda indikasikan. Jenis referensi juga mengandung dua bidang sistem. Yang pertama menyimpan 'SyncBlockIndex', yang kedua menyimpan informasi tentang jenis, termasuk informasi tentang Tabel Metode Virtual (VMT).
- Tipe referensi dapat memiliki metode yang diganti ketika diwariskan. Jenis nilai tidak dapat diwarisi.
- Anda harus mengalokasikan ruang pada tumpukan untuk contoh tipe referensi. Jenis nilai dapat dialokasikan pada tumpukan, atau itu menjadi bagian dari jenis referensi. Ini cukup meningkatkan kinerja beberapa algoritma.
Namun, ada fitur umum:
- Kedua subclass dapat mewarisi tipe objek dan menjadi perwakilannya.
Mari kita perhatikan lebih dekat setiap fitur.
Bab ini diterjemahkan dari bahasa Rusia bersama oleh penulis dan penerjemah profesional . Anda dapat membantu kami dengan terjemahan dari bahasa Rusia atau Inggris ke bahasa lain, terutama ke bahasa Cina atau Jerman.
Juga, jika Anda ingin berterima kasih kepada kami, cara terbaik yang dapat Anda lakukan adalah memberi kami bintang di github atau untuk repositori garpu
github / sidristij / dotnetbook .

Mari kita perhatikan lebih dekat setiap fitur.
Menyalin
Perbedaan utama antara kedua jenis adalah sebagai berikut:
- Setiap variabel, kelas atau bidang struktur atau parameter metode yang mengambil tipe referensi menyimpan referensi ke nilai;
- Tetapi setiap variabel, kelas atau bidang struktur atau parameter metode yang mengambil tipe nilai menyimpan nilai dengan tepat, yaitu seluruh struktur.
Ini berarti bahwa menetapkan atau meneruskan parameter ke suatu metode akan menyalin nilainya. Bahkan jika Anda mengubah salinannya, dokumen asli akan tetap sama. Namun, jika Anda mengubah bidang jenis referensi, ini akan "mempengaruhi" semua bagian dengan referensi ke turunan jenis. Mari kita lihat
contoh:
DateTime dt = DateTime.Now;
Tampaknya properti ini menghasilkan konstruksi kode ambigu seperti
perubahan kode dalam koleksi:
// Let's declare a structure struct ValueHolder { public int Data; } // Let's create an array of such structures and initialize the Data field = 5 var array = new [] { new ValueHolder { Data = 5 } }; // Let's use an index to get the structure and put 4 in the Data field array[0].Data = 4; // Let's check the value Console.WriteLine(array[0].Data);
Ada trik kecil dalam kode ini. Sepertinya kita mendapatkan instance struktur terlebih dahulu, dan kemudian menetapkan nilai baru ke bidang Data dari salinan. Ini berarti kita harus mendapatkan 5
lagi ketika memeriksa nilainya. Namun, ini tidak terjadi. MSIL memiliki instruksi terpisah untuk mengatur nilai bidang dalam struktur array, yang meningkatkan kinerja. Kode akan berfungsi sebagaimana dimaksud: program akan melakukannya
output 4
ke konsol.
Mari kita lihat apa yang akan terjadi jika kita mengubah kode ini:
// Let's declare a structure struct ValueHolder { public int Data; } // Let's create a list of such structures and initialize the Data field = 5 var list = new List<ValueHolder> { new ValueHolder { Data = 5 } }; // Let's use an index to get the structure and put 4 in the Data field list[0].Data = 4; // Let's check the value Console.WriteLine(list[0].Data);
Kompilasi kode ini akan gagal, karena ketika Anda menulis list[0].Data = 4
Anda mendapatkan salinan struktur terlebih dahulu. Bahkan, Anda memanggil metode instance dari tipe List<T>
yang mendasari akses oleh indeks. Dibutuhkan salinan struktur dari array internal ( List<T>
menyimpan data dalam array) dan mengembalikan salinan ini kepada Anda dari metode akses menggunakan indeks. Selanjutnya, Anda mencoba untuk memodifikasi salinan, yang tidak digunakan lebih jauh. Kode ini tidak ada gunanya. Kompiler melarang perilaku seperti itu, mengetahui bahwa orang menyalahgunakan tipe nilai. Kita harus menulis ulang contoh ini dengan cara berikut:
// Let's declare a structure struct ValueHolder { public int Data; } // Let's create a list of such structures and initialize the Data field = 5 var list = new List<ValueHolder> { new ValueHolder { Data = 5 } }; // Let's use an index to get the structure and put 4 in the Data field. Then, let's save it again. var copy = list[0]; copy.Data = 4; list[0] = copy; // Let's check the value Console.WriteLine(list[0].Data);
Kode ini benar meskipun tampak berlebihan. Program akan melakukannya
output 4
ke konsol.
Contoh berikutnya menunjukkan apa yang saya maksud dengan βnilai struktur adalah
seluruh struktur β
// Variant 1 struct PersonInfo { public int Height; public int Width; public int HairColor; } int x = 5; PersonInfo person; int y = 6; // Variant 2 int x = 5; int Height; int Width; int HairColor; int y = 6;
Kedua contoh serupa dalam hal lokasi data dalam memori, karena nilai struktur adalah seluruh struktur. Ini mengalokasikan memori untuk dirinya sendiri di mana ia berada.
// Variant 1 struct PersonInfo { public int Height; public int Width; public int HairColor; } class Employee { public int x; public PersonInfo person; public int y; } // Variant 2 class Employee { public int x; public int Height; public int Width; public int HairColor; public int y; }
Contoh-contoh ini juga serupa dalam hal lokasi elemen dalam memori karena struktur mengambil tempat yang ditentukan di antara bidang kelas. Saya tidak mengatakan mereka sangat mirip karena Anda dapat mengoperasikan bidang struktur menggunakan metode struktur.
Tentu saja, ini bukan jenis referensi. Sebuah instance itu sendiri adalah pada Small Object Heap (SOH) yang tidak terjangkau atau Large Object Heap (LOH). Bidang kelas hanya berisi nilai pointer ke instance: angka 32 atau 64-bit.
Mari kita lihat contoh terakhir untuk menutup masalah ini.
// Variant 1 struct PersonInfo { public int Height; public int Width; public int HairColor; } void Method(int x, PersonInfo person, int y); // Variant 2 void Method(int x, int HairColor, int Width, int Height, int y);
Dalam hal memori kedua varian kode akan bekerja dengan cara yang sama, tetapi tidak dalam hal arsitektur. Ini bukan hanya penggantian sejumlah variabel argumen. Urutan berubah karena parameter metode dideklarasikan satu demi satu. Mereka diletakkan di tumpukan dengan cara yang sama.
Namun, tumpukan tumbuh dari alamat yang lebih tinggi ke yang lebih rendah. Itu berarti urutan mendorong sepotong demi sepotong struktur akan berbeda dari mendorongnya secara keseluruhan.
Metode dan warisan yang dapat ditimpa
Perbedaan besar berikutnya antara kedua jenis adalah kurangnya virtual
metode tabel dalam struktur. Ini berarti:
- Anda tidak bisa mendeskripsikan dan menimpa metode virtual dalam struktur.
- Suatu struktur tidak dapat mewarisi yang lain. Satu-satunya cara untuk meniru warisan adalah dengan meletakkan struktur tipe dasar di bidang pertama. Bidang-bidang struktur "warisan" akan pergi setelah bidang struktur "dasar" dan itu akan membuat warisan logis. Bidang kedua struktur akan bertepatan berdasarkan offset.
- Anda dapat meneruskan struktur ke kode yang tidak dikelola. Namun, Anda akan kehilangan informasi tentang metode. Ini karena struktur hanyalah ruang dalam memori, diisi dengan data tanpa informasi tentang suatu jenis. Anda dapat meneruskannya ke metode yang tidak dikelola, misalnya, ditulis dalam C ++, tanpa perubahan.
Kurangnya tabel metode virtual mengurangi bagian tertentu dari "sihir" warisan dari struktur tetapi memberi mereka keuntungan lain. Yang pertama adalah kita dapat melewatkan instance dari struktur seperti itu ke lingkungan eksternal (di luar .NET Framework). Ingat, ini hanya kenangan
jangkauan! Kami juga dapat mengambil rentang memori dari kode yang tidak dikelola dan melemparkan tipe ke struktur kami untuk membuat bidangnya lebih mudah diakses. Anda tidak dapat melakukan ini dengan kelas karena mereka memiliki dua bidang yang tidak dapat diakses. Ini adalah SyncBlockIndex dan alamat tabel metode virtual. Jika kedua bidang lolos ke kode yang tidak dikelola, itu akan berbahaya. Menggunakan tabel metode virtual seseorang dapat mengakses jenis apa pun dan mengubahnya untuk menyerang aplikasi.
Mari kita tunjukkan itu hanya rentang memori tanpa logika tambahan.
unsafe void Main() { int secret = 666; HeightHolder hh; hh.Height = 5; WidthHolder wh; unsafe { // This cast wouldn't work if structures had the information about a type. // The CLR would check a hierarchy before casting a type and if it didn't find WidthHolder, // it would output an InvalidCastException exception. But since a structure is a memory range, // you can interpret it as any kind of structure. wh = *(WidthHolder*)&hh; } Console.WriteLine("Width: " + wh.Width); Console.WriteLine("Secret:" + wh.Secret); } struct WidthHolder { public int Width; public int Secret; } struct HeightHolder { public int Height; }
Di sini, kami melakukan operasi yang tidak mungkin dilakukan dalam pengetikan yang kuat. Kami melemparkan satu jenis ke jenis lain yang tidak kompatibel yang berisi satu bidang tambahan. Kami memperkenalkan variabel tambahan di dalam metode Utama. Secara teori, nilainya rahasia. Namun, kode contoh akan menampilkan nilai variabel, tidak ditemukan dalam struktur di dalam metode Main()
. Anda mungkin menganggapnya sebagai pelanggaran keamanan, tetapi berbagai hal tidak begitu sederhana. Anda tidak dapat menghilangkan kode yang tidak dikelola dalam suatu program. Alasan utamanya adalah struktur tumpukan benang. Seseorang dapat menggunakannya untuk mengakses kode yang tidak dikelola dan bermain dengan variabel lokal. Anda dapat mempertahankan kode Anda dari serangan ini dengan mengacak ukuran bingkai tumpukan. Atau, Anda dapat menghapus informasi tentang register EBP
untuk mempersulit pengembalian frame stack. Namun, ini tidak masalah bagi kami sekarang. Yang kami minati dalam contoh ini adalah sebagai berikut. Variabel "rahasia" berjalan sebelum definisi variabel jam dan setelahnya dalam struktur WidthHolder (di tempat yang berbeda, sebenarnya). Jadi mengapa kita dengan mudah mendapatkan nilainya? Jawabannya adalah tumpukan itu tumbuh dari kanan ke kiri. Variabel yang dideklarasikan pertama akan memiliki alamat yang jauh lebih tinggi, dan yang dinyatakan kemudian akan memiliki alamat yang lebih rendah.
Perilaku saat memanggil metode instance
Kedua tipe data memiliki fitur lain yang tidak jelas untuk dilihat dan dapat menjelaskan struktur kedua tipe tersebut. Ini berkaitan dengan memanggil metode instance.
// The example with a reference type class FooClass { private int x; public void ChangeTo(int val) { x = val; } } // The example with a value type struct FooStruct { private int x; public void ChangeTo(int val) { x = val; } } FooClass klass = new FooClass(); FooStruct strukt = new FooStruct(); klass.ChangeTo(10); strukt.ChangeTo(10);
Secara logis, kita dapat memutuskan bahwa metode ini memiliki satu tubuh yang dikompilasi. Dengan kata lain, tidak ada instance dari tipe yang memiliki seperangkat metode yang dikompilasi sendiri, mirip dengan set instance lainnya. Namun, metode yang dipanggil tahu instance miliknya sebagai referensi ke instance tipe adalah parameter pertama. Kita dapat menulis ulang contoh kita dan itu akan sama dengan apa yang kita katakan sebelumnya. Saya tidak menggunakan contoh dengan metode virtual dengan sengaja, karena mereka memiliki prosedur lain.
// An example with a reference type class FooClass { public int x; } // An example with a value type struct FooStruct { public int x; } public void ChangeTo(FooClass klass, int val) { klass.x = val; } public void ChangeTo(ref FooStruct strukt, int val) { strukt.x = val; } FooClass klass = new FooClass(); FooStruct strukt = new FooStruct(); ChangeTo(klass, 10); ChangeTo(ref strukt, 10);
Saya harus menjelaskan penggunaan kata kunci ref. Jika saya tidak menggunakannya, saya akan mendapatkan salinan struktur sebagai parameter metode, bukan yang asli. Lalu saya akan mengubahnya, tetapi yang asli akan tetap sama. Saya harus mengembalikan salinan yang diubah dari metode ke penelepon (penyalinan lain), dan penelepon akan menyimpan nilai ini kembali dalam variabel (satu lagi penyalinan). Sebagai gantinya, metode instance mendapat pointer dan menggunakannya untuk langsung mengubah yang asli. Menggunakan pointer tidak mempengaruhi kinerja karena operasi tingkat prosesor menggunakan pointer. Ref adalah bagian dari dunia C #, tidak ada lagi.
Kemampuan untuk menunjuk ke posisi elemen.
Baik struktur dan kelas memiliki kemampuan lain untuk menunjuk ke offset bidang tertentu sehubungan dengan awal struktur dalam memori. Ini melayani beberapa tujuan:
- untuk bekerja dengan API eksternal di dunia yang tidak dikelola tanpa harus memasukkan bidang yang tidak digunakan sebelum yang diperlukan;
- untuk menginstruksikan kompiler untuk menemukan bidang tepat di awal jenis (
[FieldOffset(0)]
). Ini akan membuat pekerjaan dengan tipe ini lebih cepat. Jika ini adalah bidang yang sering digunakan, kita dapat meningkatkan kinerja aplikasi. Namun, ini hanya berlaku untuk tipe nilai. Dalam tipe referensi, bidang dengan offset nol berisi alamat tabel metode virtual, yang membutuhkan 1 kata mesin. Bahkan jika Anda menangani bidang pertama dari sebuah kelas, ia akan menggunakan pengalamatan yang kompleks (alamat + offset). Ini karena bidang kelas yang paling sering digunakan adalah alamat tabel metode virtual. Tabel diperlukan untuk memanggil semua metode virtual; - untuk menunjuk ke beberapa bidang menggunakan satu alamat. Dalam hal ini, nilai yang sama ditafsirkan sebagai tipe data yang berbeda. Dalam C ++ tipe data ini disebut gabungan;
- jangan repot-repot mendeklarasikan apa pun: kompiler akan mengalokasikan bidang secara optimal. Dengan demikian, urutan akhir bidang mungkin berbeda.
Komentar umum
- Otomatis : lingkungan run-time secara otomatis memilih lokasi dan pengepakan untuk semua bidang kelas atau struktur. Struktur yang ditentukan yang ditandai oleh anggota enumerasi ini tidak dapat masuk ke kode yang tidak dikelola. Upaya untuk melakukannya akan menghasilkan pengecualian;
- Eksplisit : seorang programmer secara eksplisit mengontrol lokasi yang tepat dari setiap bidang jenis dengan FieldOffsetAttribute;
- Berurutan : anggota tipe datang dalam urutan berurutan, didefinisikan selama desain tipe. Nilai StructLayoutAttribute.Pack dari langkah pengemasan menunjukkan lokasi mereka.
Menggunakan FieldOffset untuk melewati bidang struktur yang tidak digunakan
Struktur yang berasal dari dunia yang tidak dikelola dapat berisi bidang yang dicadangkan. Orang dapat menggunakannya di versi perpustakaan yang akan datang. Di C / C ++ kami mengisi celah ini dengan menambahkan bidang, mis. Reserved1, reserved2, ... Namun, dalam. NET kami hanya mengimbangi ke awal bidang dengan menggunakan atribut FieldOffsetAttribute dan [StructLayout(LayoutKind.Explicit)]
.
[StructLayout(LayoutKind.Explicit)] public struct SYSTEM_INFO { [FieldOffset(0)] public ulong OemId; // 92 bytes reserved [FieldOffset(100)] public ulong PageSize; [FieldOffset(108)] public ulong ActiveProcessorMask; [FieldOffset(116)] public ulong NumberOfProcessors; [FieldOffset(124)] public ulong ProcessorType; }
Sebuah celah ditempati tetapi ruang yang tidak digunakan. Struktur akan memiliki ukuran yang sama dengan 132 dan bukan 40 byte seperti yang terlihat dari awal.
Serikat pekerja
Menggunakan FieldOffsetAttribute Anda bisa meniru tipe C / C ++ yang disebut union. Hal ini memungkinkan untuk mengakses data yang sama dengan entitas
berbagai jenis. Mari kita lihat contohnya:
// If we read the RGBA.Value, we will get an Int32 value accumulating all // other fields. // However, if we try to read the RGBA.R, RGBA.G, RGBA.B, RGBA.Alpha, we // will get separate components of Int32. [StructLayout(LayoutKind.Explicit)] public struct RGBA { [FieldOffset(0)] public uint Value; [FieldOffset(0)] public byte R; [FieldOffset(1)] public byte G; [FieldOffset(2)] public byte B; [FieldOffset(3)] public byte Alpha; }
Anda mungkin mengatakan perilaku seperti itu hanya mungkin untuk tipe nilai. Namun, Anda dapat mensimulasikannya untuk jenis referensi, menggunakan satu alamat untuk tumpang tindih dua jenis referensi atau satu jenis referensi dan satu jenis nilai:
class Program { public static void Main() { Union x = new Union(); x.Reference.Value = "Hello!"; Console.WriteLine(x.Value.Value); } [StructLayout(LayoutKind.Explicit)] public class Union { public Union() { Value = new Holder<IntPtr>(); Reference = new Holder<object>(); } [FieldOffset(0)] public Holder<IntPtr> Value; [FieldOffset(0)] public Holder<object> Reference; } public class Holder<T> { public T Value; } }
Saya menggunakan tipe generik untuk tumpang tindih dengan sengaja. Kalau dulu saya biasa
tumpang tindih, tipe ini akan menyebabkan TypeLoadException ketika dimuat dalam domain aplikasi. Ini mungkin terlihat seperti pelanggaran keamanan secara teori (terutama, ketika berbicara tentang plug-in aplikasi), tetapi jika kita mencoba menjalankan kode ini menggunakan domain yang dilindungi, kita akan mendapatkan TypeLoadException
sama.
Perbedaan dalam alokasi
Fitur lain yang membedakan kedua jenis adalah alokasi memori untuk objek atau struktur. CLR harus memutuskan beberapa hal sebelum mengalokasikan memori untuk suatu objek. Berapa ukuran suatu objek? Apakah kurang lebih 85K? Jika kurang, maka apakah ada cukup ruang kosong pada SOH untuk mengalokasikan objek ini? Jika lebih, CLR mengaktifkan Pengumpul Sampah. Ini melewati grafik objek, memadatkan objek dengan memindahkannya ke ruang kosong. Jika masih tidak ada ruang pada SOH, alokasi halaman memori virtual tambahan akan dimulai. Hanya saat itulah sebuah objek mendapat ruang yang dialokasikan, dibersihkan dari sampah. Setelah itu, CLR menjabarkan SyncBlockIndex dan VirtualMethodsTable. Akhirnya, referensi ke suatu objek kembali ke pengguna.
Jika objek yang dialokasikan lebih besar dari 85K, ia pergi ke Large Objects Heap (LOH). Ini adalah kasus string dan array besar. Di sini, kita harus menemukan ruang yang paling cocok dalam memori dari daftar rentang yang tidak dihuni atau mengalokasikan yang baru. Ini tidak cepat, tetapi kita akan berurusan dengan benda-benda sebesar itu dengan hati-hati. Juga, kita tidak akan membicarakannya di sini.
Ada beberapa skenario yang mungkin untuk RefTypes:
- RefType <85K, ada ruang di SOH: alokasi memori cepat;
- RefType <85K, ruang pada SOH hampir habis: alokasi memori sangat lambat;
- RefType> 85K, alokasi memori lambat.
Operasi seperti itu jarang terjadi dan tidak dapat bersaing dengan ValTip. Algoritma alokasi memori untuk tipe nilai tidak ada. Alokasi memori untuk tipe nilai tidak dikenai biaya. Satu-satunya hal yang terjadi ketika mengalokasikan memori untuk jenis ini adalah mengatur bidang ke nol. Mari kita lihat mengapa ini terjadi: 1. Ketika seseorang mendeklarasikan variabel dalam tubuh metode, waktu alokasi memori untuk suatu struktur mendekati nol. Itu karena waktu alokasi untuk variabel lokal tidak tergantung pada jumlah mereka; 2. Jika ValTip dialokasikan sebagai bidang, Reftip akan menambah ukuran bidang. Tipe nilai dialokasikan sepenuhnya, menjadi bagiannya; 3. Seperti dalam hal penyalinan, jika ValTip dilewatkan sebagai parameter metode, akan muncul perbedaan, tergantung pada ukuran dan lokasi parameter.
Namun, itu tidak memakan waktu lebih lama daripada menyalin satu variabel ke variabel lain.
Pilihan antara kelas atau struktur
Mari kita bahas kelebihan dan kekurangan dari kedua jenis dan memutuskan skenario penggunaannya. Prinsip klasik mengatakan kita harus memilih tipe nilai jika tidak lebih besar dari 16 byte, tetap tidak berubah selama masa pakaiannya dan tidak diwariskan. Namun, memilih jenis yang tepat berarti meninjaunya dari perspektif yang berbeda berdasarkan skenario penggunaan masa depan. Saya mengusulkan tiga kelompok kriteria:
- berdasarkan arsitektur sistem tipe, di mana tipe Anda akan berinteraksi;
- berdasarkan pendekatan Anda sebagai pemrogram sistem untuk memilih tipe dengan kinerja optimal;
- ketika tidak ada pilihan lain.
Setiap fitur yang dirancang harus mencerminkan tujuannya. Ini tidak hanya berurusan dengan nama atau antarmuka interaksi (metode, properti). Orang dapat menggunakan pertimbangan arsitektur untuk memilih antara nilai dan jenis referensi. Mari kita pikirkan mengapa struktur dan bukan kelas dapat dipilih dari sudut pandang sistem tipe sistem.
Jika tipe yang Anda rancang bersifat agnostik terhadap kondisinya, ini berarti kondisinya mencerminkan suatu proses atau nilai sesuatu. Dengan kata lain, turunan tipe konstan dan tidak dapat diubah secara alami. Kita dapat membuat instance tipe lain berdasarkan konstanta ini dengan menunjukkan beberapa offset. Atau, kita dapat membuat instance baru dengan menunjukkan propertinya. Namun, kita tidak boleh mengubahnya. Saya tidak bermaksud bahwa struktur adalah tipe yang tidak dapat diubah. Anda dapat mengubah nilai bidangnya. Selain itu, Anda bisa meneruskan referensi ke struktur ke metode menggunakan parameter ref dan Anda akan mendapatkan bidang yang diubah setelah keluar dari metode. Yang saya bicarakan di sini adalah pengertian arsitektur. Saya akan memberikan beberapa contoh.
- DateTime adalah struktur yang merangkum konsep momen dalam waktu. Ini menyimpan data ini sebagai uint tetapi memberikan akses ke karakteristik yang terpisah dari suatu saat: tahun, bulan, hari, jam, menit, detik, milidetik dan bahkan kutu prosesor. Namun, itu tidak dapat diubah, berdasarkan pada apa yang merangkumnya. Kami tidak dapat mengubah momen dalam waktu. Aku tidak bisa hidup pada menit berikutnya seolah itu adalah ulang tahun terbaikku di masa kecil. Jadi, jika kita memilih tipe data, kita bisa memilih kelas dengan antarmuka hanya baca, yang menghasilkan contoh baru untuk setiap perubahan properti. Atau, kita dapat memilih struktur, yang bisa tetapi tidak boleh mengubah bidang instansinya: nilainya adalah deskripsi saat dalam waktu, seperti angka. Anda tidak dapat mengakses struktur angka dan mengubahnya. Jika Anda ingin mendapatkan momen lain dalam waktu, yang berbeda satu hari dari aslinya, Anda hanya akan mendapatkan contoh baru dari suatu struktur.
KeyValuePair<TKey, TValue>
adalah struktur yang merangkum konsep pasangan kunci - nilai yang terhubung. Struktur ini hanya untuk menampilkan konten kamus selama enumerasi. Dari sudut pandang arsitektur kunci dan nilai adalah konsep yang tidak dapat dipisahkan dalam Dictionary<T>
. Namun, di dalam kita memiliki struktur yang kompleks, di mana kunci terletak terpisah dari suatu nilai. Untuk pengguna, pasangan kunci-nilai adalah konsep yang tidak dapat dipisahkan dalam hal antarmuka dan arti dari struktur data. Ini adalah seluruh nilai itu sendiri. Jika seseorang memberikan nilai lain untuk kunci, seluruh pasangan akan berubah. Dengan demikian, mereka mewakili satu kesatuan. Ini membuat struktur varian ideal dalam kasus ini.
Jika tipe yang Anda rancang adalah bagian yang tidak terpisahkan dari tipe eksternal tetapi merupakan bagian yang tidak terpisahkan secara struktural. Itu berarti tidak benar untuk mengatakan tipe eksternal mengacu pada turunan dari tipe yang dienkapsulasi. Namun, benar untuk mengatakan bahwa tipe enkapsulasi adalah bagian dari eksternal bersama dengan semua propertinya. Ini berguna ketika mendesain struktur yang merupakan bagian dari struktur lain.
- Misalnya, jika kita mengambil struktur header file, maka tidak pantas untuk meneruskan referensi dari satu file ke yang lain, misalnya beberapa file header.txt. Ini akan sesuai ketika memasukkan dokumen ke dokumen lain, bukan dengan menyematkan file tetapi menggunakan referensi dalam sistem file. Contoh yang baik adalah file pintasan di OS Windows. Namun, jika kita berbicara tentang tajuk file (misalnya tajuk file JPEG yang berisi metadata tentang ukuran gambar, metode kompresi, parameter fotografi, koordinat GPS dan lainnya), maka kita harus menggunakan struktur untuk merancang jenis parsing header. Jika Anda menggambarkan semua header dalam struktur, Anda akan mendapatkan posisi bidang yang sama di memori seperti di file. Dengan menggunakan transformasi
*(Header *)readedBuffer
sederhana yang tidak aman tanpa deserialisasi, Anda akan mendapatkan struktur data yang terisi penuh.
- Tidak ada contoh yang menunjukkan warisan perilaku. Mereka menunjukkan bahwa tidak perlu mewarisi perilaku entitas ini. Mereka mandiri. Namun, jika kita mempertimbangkan efektivitas kode, kita akan melihat pilihan dari sisi lain:
- Jika kita perlu mengambil beberapa data terstruktur dari kode yang tidak dikelola, kita harus memilih struktur. Kami juga dapat meneruskan struktur data ke metode yang tidak aman. Tipe referensi sama sekali tidak cocok untuk ini.
- Struktur adalah pilihan Anda jika suatu tipe melewati data dalam pemanggilan metode (sebagai nilai yang dikembalikan atau sebagai parameter metode) dan tidak perlu merujuk ke nilai yang sama dari tempat yang berbeda. Contoh sempurna adalah tupel. Jika suatu metode mengembalikan beberapa nilai menggunakan tuple, itu akan mengembalikan ValueTuple, yang dinyatakan sebagai struktur. Metode ini tidak akan mengalokasikan ruang pada heap, tetapi akan menggunakan tumpukan utas, di mana alokasi memori tidak ada biaya.
- Jika Anda mendesain sistem yang menciptakan lalu lintas besar instance yang memiliki ukuran kecil dan masa pakai, menggunakan tipe referensi akan mengarah ke kumpulan objek atau, jika tanpa kumpulan objek, ke akumulasi sampah yang tidak terkendali di heap. Beberapa objek akan berubah menjadi generasi yang lebih tua, menambah beban pada GC. Menggunakan tipe nilai di tempat-tempat seperti itu (jika memungkinkan) akan memberikan peningkatan kinerja karena tidak ada yang lolos ke SOH. Ini akan mengurangi beban pada GC dan algoritme akan bekerja lebih cepat;
Mendasarkan pada apa yang saya katakan, berikut adalah beberapa saran untuk menggunakan struktur:
- Saat memilih koleksi Anda harus menghindari array besar menyimpan struktur besar. Ini termasuk struktur data berdasarkan array. Ini dapat menyebabkan transisi ke Tumpukan Objek Besar dan fragmentasi. Adalah salah untuk berpikir bahwa jika struktur kita memiliki 4 bidang tipe byte, itu akan mengambil 4 byte. Kita harus memahami bahwa dalam sistem 32-bit setiap bidang struktur disejajarkan pada batas 4 byte (setiap bidang alamat harus dibagi persis dengan 4) dan dalam sistem 64-bit - pada batas 8 byte. Ukuran array harus bergantung pada ukuran struktur dan platform, menjalankan program. Dalam contoh kita dengan 4 byte - 85K / (dari 4 hingga 8 byte per bidang * jumlah bidang = 4) minus ukuran header array sama dengan sekitar 2 600 elemen per array tergantung pada platform (ini harus dibulatkan ke bawah ) Itu tidak terlalu banyak. Mungkin kelihatannya kita bisa dengan mudah mencapai konstanta ajaib 20.000 elemen
- Kadang-kadang Anda menggunakan struktur ukuran besar sebagai sumber data dan menempatkannya sebagai bidang dalam kelas, sambil memiliki satu salinan direplikasi untuk menghasilkan ribuan contoh. Lalu Anda perluas setiap instance kelas untuk ukuran struktur. Ini akan menyebabkan pembengkakan generasi nol dan transisi ke generasi satu dan bahkan dua. Jika instance kelas memiliki masa hidup pendek dan Anda berpikir GC akan mengumpulkannya pada generasi nol - selama 1 ms, Anda akan kecewa. Mereka sudah dalam generasi satu dan bahkan dua. Ini membuat perbedaan. Jika GC mengumpulkan generasi nol selama 1 ms, generasi satu dan dua dikumpulkan dengan sangat lambat yang akan menyebabkan penurunan efisiensi;
- Untuk alasan yang sama Anda harus menghindari melewati struktur besar melalui serangkaian pemanggilan metode. Jika semua elemen saling memanggil satu sama lain, panggilan ini akan mengambil lebih banyak ruang di stack dan membuat aplikasi Anda mati oleh StackOverflowException. Alasan selanjutnya adalah kinerja. Semakin banyak salinan, semakin lambat semuanya berfungsi.
Itu sebabnya pemilihan tipe data bukanlah proses yang jelas. Seringkali, ini bisa merujuk pada optimasi prematur, yang tidak dianjurkan. Namun, jika Anda tahu situasi Anda termasuk dalam prinsip-prinsip yang disebutkan di atas, Anda dapat dengan mudah memilih tipe nilai.
Bab ini diterjemahkan dari bahasa Rusia bersama oleh penulis dan penerjemah profesional . Anda dapat membantu kami dengan terjemahan dari bahasa Rusia atau Inggris ke bahasa lain, terutama ke bahasa Cina atau Jerman.
Juga, jika Anda ingin berterima kasih kepada kami, cara terbaik yang dapat Anda lakukan adalah memberi kami bintang di github atau untuk repositori garpu
github / sidristij / dotnetbook .