Mengapa
Untuk memiliki sistem tipe terpadu dan memungkinkan tipe nilai memiliki representasi yang sama sekali berbeda dari data dasar mereka dari cara tipe referensi mewakili data dasar mereka (misalnya, int
hanya seember tiga puluh dua bit yang benar-benar berbeda dari referensi Tipe).
Pikirkan seperti ini. Anda memiliki variabel o
tipe object
. Dan sekarang Anda memiliki int
dan ingin memasukkannya ke dalam o
. o
adalah referensi ke sesuatu di suatu tempat, dan dengan int
tegas bukan referensi ke sesuatu di suatu tempat (setelah semua, itu hanya angka). Jadi, apa yang Anda lakukan adalah ini: Anda membuat yang baru object
yang dapat menyimpan int
dan kemudian Anda menetapkan referensi ke objek itu o
. Kami menyebut proses ini "tinju."
Jadi, jika Anda tidak peduli tentang memiliki sistem tipe terpadu (yaitu, tipe referensi dan tipe nilai memiliki representasi yang sangat berbeda dan Anda tidak ingin cara umum untuk "mewakili" keduanya) maka Anda tidak perlu bertinju. Jika Anda tidak peduli untuk int
mewakili nilai dasarnya (yaitu, alih-alih int
menjadi tipe referensi juga dan simpan referensi ke nilai dasarnya) maka Anda tidak perlu bertinju.
di mana saya harus menggunakannya.
Misalnya, tipe koleksi lama ArrayList
hanya memakan object
s. Artinya, itu hanya menyimpan referensi ke sesuatu yang tinggal di suatu tempat. Tanpa tinju Anda tidak dapat memasukkan int
koleksi seperti itu. Tapi dengan tinju, kamu bisa.
Sekarang, di zaman generik Anda tidak benar-benar membutuhkan ini dan secara umum dapat berjalan dengan riang tanpa memikirkan masalahnya. Tetapi ada beberapa peringatan yang harus diperhatikan:
Ini benar:
double e = 2.718281828459045;
int ee = (int)e;
Ini bukan:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
Sebaliknya, Anda harus melakukan ini:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
Pertama kita harus secara eksplisit menghapus kotaknya double
( (double)o
) dan kemudian melemparkannya ke int
.
Apa hasil dari hal berikut:
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
Pikirkan sejenak sebelum melanjutkan ke kalimat berikutnya.
Jika Anda mengatakan True
dan False
hebat! Tunggu apa? Itu karena ==
pada tipe referensi menggunakan referensi-kesetaraan yang memeriksa apakah referensi sama, bukan jika nilai yang mendasarinya sama. Ini adalah kesalahan yang sangat mudah dilakukan. Mungkin bahkan lebih halus
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
juga akan dicetak False
!
Lebih baik dikatakan:
Console.WriteLine(o1.Equals(o2));
yang kemudian akan, untungnya, dicetak True
.
Satu kehalusan terakhir:
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
Apa outputnya? Tergantung! Jika Point
a struct
maka outputnya adalah 1
tetapi jika Point
a class
maka outputnya adalah 2
! Konversi tinju membuat salinan nilai yang sedang kotak menjelaskan perbedaan dalam perilaku.
boxing
danunboxing
?Dalam .NET framework, ada dua jenis tipe - tipe nilai dan tipe referensi. Ini relatif umum dalam bahasa OO.
Salah satu fitur penting dari bahasa berorientasi objek adalah kemampuan untuk menangani instance dengan cara tipe-agnostik. Ini disebut polimorfisme . Karena kami ingin mengambil keuntungan dari polimorfisme, tetapi kami memiliki dua jenis spesies yang berbeda, harus ada beberapa cara untuk menyatukan mereka sehingga kami dapat menangani satu atau yang lain dengan cara yang sama.
Sekarang, kembali ke masa lalu (1.0 dari Microsoft.NET), tidak ada hullabaloo generik bermodel baru ini. Anda tidak bisa menulis metode yang memiliki argumen tunggal yang bisa melayani tipe nilai dan tipe referensi. Itu pelanggaran polimorfisme. Jadi tinju diadopsi sebagai sarana untuk memaksa tipe nilai menjadi objek.
Jika ini tidak memungkinkan, kerangka kerja akan dipenuhi dengan metode dan kelas yang satu-satunya tujuan adalah untuk menerima spesies jenis lain. Tidak hanya itu, tetapi karena tipe nilai tidak benar-benar berbagi leluhur tipe yang sama, Anda harus memiliki kelebihan metode yang berbeda untuk setiap tipe nilai (bit, byte, int16, int32, dll, dll.).
Tinju mencegah hal ini terjadi. Dan itu sebabnya Inggris merayakan Boxing Day.
sumber
List<string>.Enumerator
untukIEnumerator<string>
menghasilkan sebuah objek yang sebagian besar berperilaku seperti jenis kelas, tetapi dengan patahEquals
metode. Cara yang lebih baik untuk corList<string>.Enumerator
untukIEnumerator<string>
akan memanggil operator konversi kustom, tetapi keberadaan mencegah konversi tersirat itu.Cara terbaik untuk memahami ini adalah dengan melihat bahasa pemrograman tingkat rendah yang dibangun oleh C #.
Dalam bahasa tingkat terendah seperti C, semua variabel masuk satu tempat: Stack. Setiap kali Anda mendeklarasikan variabel, variabel itu berada di Stack. Mereka hanya bisa berupa nilai primitif, seperti bool, byte, int 32-bit, uint 32-bit, dll. Stack sederhana dan cepat. Sebagai variabel ditambahkan mereka hanya pergi satu di atas yang lain, jadi yang pertama Anda mendeklarasikan duduk katakanlah, 0x00, berikutnya pada 0x01, berikutnya pada 0x02 dalam RAM, dll. Selain itu, variabel sering dialamatkan di compile- waktu, sehingga alamat mereka diketahui bahkan sebelum Anda menjalankan program.
Di tingkat berikutnya, seperti C ++, struktur memori kedua yang disebut Heap diperkenalkan. Anda sebagian besar masih tinggal di Stack, tetapi int khusus disebut Pointer dapat ditambahkan ke Stack, yang menyimpan alamat memori untuk byte pertama dari sebuah Object, dan Object tersebut tinggal di Heap. Heap agak berantakan dan agak mahal untuk dipertahankan, karena tidak seperti variabel Stack, mereka tidak menumpuk secara linear ke atas dan kemudian turun ketika program dijalankan. Mereka dapat datang dan pergi tanpa urutan tertentu, dan mereka dapat tumbuh dan menyusut.
Berurusan dengan pointer sulit. Mereka adalah penyebab kebocoran memori, buffer overruns, dan frustrasi. C # untuk menyelamatkan.
Pada level yang lebih tinggi, C #, Anda tidak perlu memikirkan pointer - kerangka .Net (ditulis dalam C ++) memikirkan ini untuk Anda dan menyajikannya kepada Anda sebagai Referensi untuk Objek, dan untuk kinerja, memungkinkan Anda menyimpan nilai yang lebih sederhana seperti bools, bytes, dan ints sebagai Value Type. Di bawah kap, Obyek dan barang-barang yang instantiates Kelas pergi pada Heap, mahal-Managed Heap, sementara Tipe Nilai pergi di tumpukan yang sama yang Anda miliki di C tingkat rendah - super cepat.
Demi menjaga interaksi antara 2 konsep memori yang berbeda (dan strategi penyimpanan) ini secara sederhana dari perspektif pembuat kode, Jenis Nilai dapat Dikemas kapan saja. Boxing menyebabkan nilai yang akan disalin dari Stack, dimasukkan ke dalam Object, dan ditempatkan di Heap - lebih mahal, tetapi, interaksi yang lancar dengan dunia Reference. Seperti jawaban lain tunjukkan, ini akan terjadi ketika Anda misalnya mengatakan:
Ilustrasi kuat tentang keuntungan Boxing adalah cek untuk nol:
Objek kami secara teknis adalah alamat di Stack yang menunjuk ke salinan bool b kami, yang telah disalin ke Heap. Kita dapat mengecek o untuk null karena bool telah di-Box dan diletakkan di sana.
Secara umum Anda harus menghindari Boxing kecuali Anda membutuhkannya, misalnya untuk melewatkan int / bool / apa pun sebagai objek argumen. Ada beberapa struktur dasar di. Net yang masih menuntut melewati Jenis Nilai sebagai objek (dan karenanya memerlukan Boxing), tetapi untuk sebagian besar Anda tidak perlu Box.
Daftar struktur C # historis yang tidak lengkap yang membutuhkan Boxing, yang harus Anda hindari:
Sistem Acara ternyata memiliki Kondisi Balap dalam penggunaan naif itu, dan itu tidak mendukung async. Tambahkan masalah Tinju dan mungkin harus dihindari. (Anda bisa menggantinya misalnya dengan sistem acara async yang menggunakan Generics.)
Model Threading dan Timer lama memaksa Box pada parameter mereka tetapi telah digantikan oleh async / wait yang jauh lebih bersih dan lebih efisien.
Koleksi .Net 1.1 sepenuhnya mengandalkan Boxing, karena mereka datang sebelum Generics. Ini masih menendang di System.Collections. Dalam kode baru apa pun Anda harus menggunakan Koleksi dari System.Collections.Generic, yang selain menghindari Tinju juga memberi Anda keamanan jenis yang lebih kuat .
Anda harus menghindari menyatakan atau melewati Jenis Nilai Anda sebagai objek, kecuali jika Anda harus berurusan dengan masalah historis di atas yang memaksa Boxing, dan Anda ingin menghindari hit kinerja Boxing itu nanti ketika Anda tahu itu akan menjadi Boxed pula.
Saran Per Mikael di bawah ini:
Melakukan hal ini
Bukan ini
Memperbarui
Jawaban ini awalnya menyarankan Int32, Bool dll menyebabkan tinju, padahal sebenarnya itu adalah alias sederhana untuk Jenis Nilai. Artinya, .Net memiliki tipe seperti Bool, Int32, String, dan C # alias untuk bool, int, string, tanpa perbedaan fungsional.
sumber
Boxing sebenarnya bukan sesuatu yang Anda gunakan - itu adalah sesuatu yang digunakan runtime sehingga Anda dapat menangani tipe referensi dan nilai dengan cara yang sama bila diperlukan. Misalnya, jika Anda menggunakan ArrayList untuk menyimpan daftar bilangan bulat, bilangan bulat itu kotak agar sesuai dengan slot tipe objek di ArrayList.
Menggunakan koleksi generik sekarang, ini cukup banyak hilang. Jika Anda membuat
List<int>
, tidak ada tinju yang dilakukan -List<int>
dapat menahan bilangan bulat secara langsung.sumber
Boxing dan Unboxing secara khusus digunakan untuk memperlakukan objek tipe nilai sebagai tipe referensi; memindahkan nilai aktual mereka ke tumpukan yang dikelola dan mengakses nilainya dengan referensi.
Tanpa tinju dan unboxing Anda tidak akan pernah bisa melewati tipe-nilai dengan referensi; dan itu berarti Anda tidak bisa meneruskan tipe nilai sebagai instance Object.
sumber
Tempat terakhir saya harus menghapus sesuatu adalah ketika menulis beberapa kode yang mengambil beberapa data dari database (saya tidak menggunakan LINQ ke SQL , hanya ADO.NET tua biasa ):
Pada dasarnya, jika Anda bekerja dengan API lama sebelum obat generik, Anda akan menemukan tinju. Selain itu, itu tidak umum.
sumber
Diperlukan tinju, ketika kita memiliki fungsi yang membutuhkan objek sebagai parameter, tetapi kita memiliki tipe nilai berbeda yang perlu dilewati, dalam hal itu kita perlu terlebih dahulu mengonversi tipe nilai ke tipe data objek sebelum meneruskannya ke fungsi.
Saya tidak berpikir itu benar, coba ini sebagai gantinya:
Itu berjalan dengan baik, saya tidak menggunakan tinju / unboxing. (Kecuali jika kompiler melakukannya di belakang layar?)
sumber
Di .net, setiap instance Object, atau tipe apa pun yang diturunkan darinya, menyertakan struktur data yang berisi informasi tentang tipenya. Jenis nilai "nyata" dalam .net tidak mengandung informasi semacam itu. Untuk memungkinkan data dalam tipe nilai untuk dimanipulasi oleh rutinitas yang mengharapkan untuk menerima jenis yang berasal dari objek, sistem secara otomatis menentukan untuk setiap jenis nilai tipe kelas yang sesuai dengan anggota dan bidang yang sama. Boxing menciptakan instance baru dari tipe kelas ini, menyalin bidang dari instance tipe nilai. Menghapus kotak menyalin bidang dari turunan tipe kelas ke turunan tipe nilai. Semua tipe kelas yang dibuat dari tipe nilai berasal dari kelas ValueType yang ironisnya dinamai (yang, terlepas dari namanya, sebenarnya adalah tipe referensi).
sumber
Ketika suatu metode hanya menggunakan tipe referensi sebagai parameter (katakanlah metode generik dibatasi menjadi kelas melalui
new
kendala), Anda tidak akan bisa meneruskan tipe referensi ke sana dan harus mengotakkannya.Ini juga berlaku untuk semua metode yang digunakan
object
parameter - ini harus menjadi tipe referensi.sumber
Secara umum, Anda biasanya ingin menghindari bertinju tipe nilai Anda.
Namun, ada kejadian langka di mana ini berguna. Jika Anda perlu menargetkan kerangka kerja 1.1, misalnya, Anda tidak akan memiliki akses ke koleksi umum. Setiap penggunaan koleksi di. NET 1.1 akan memerlukan memperlakukan jenis nilai Anda sebagai System.Object, yang menyebabkan tinju / unboxing.
Masih ada kasus untuk ini berguna di .NET 2.0+. Setiap kali Anda ingin memanfaatkan fakta bahwa semua tipe, termasuk tipe nilai, dapat diperlakukan sebagai objek secara langsung, Anda mungkin perlu menggunakan tinju / unboxing. Kadang-kadang ini berguna, karena memungkinkan Anda untuk menyimpan tipe apa pun dalam koleksi (dengan menggunakan objek alih-alih T dalam koleksi generik), tetapi secara umum, lebih baik untuk menghindari ini, karena Anda kehilangan keamanan jenis. Satu-satunya kasus di mana tinju sering terjadi, adalah ketika Anda menggunakan Refleksi - banyak panggilan dalam refleksi akan membutuhkan tinju / unboxing ketika bekerja dengan tipe nilai, karena tipe tersebut tidak diketahui sebelumnya.
sumber
Tinju adalah konversi nilai ke tipe referensi dengan data di beberapa offset dalam objek di heap.
Adapun apa yang sebenarnya dilakukan tinju. Berikut ini beberapa contohnya
Mono C ++
Membuka kotak di Mono adalah proses casting pointer pada offset 2 gpointer pada objek (mis. 16 byte). A
gpointer
adalah avoid*
. Ini masuk akal ketika melihat definisiMonoObject
seperti itu jelas hanya header untuk data.C ++
Untuk mengotakkan nilai dalam C ++ Anda bisa melakukan sesuatu seperti:
sumber