String adalah tipe referensi meskipun ia memiliki sebagian besar karakteristik tipe nilai seperti tidak berubah dan memiliki == kelebihan muatan untuk membandingkan teks daripada memastikan mereka mereferensikan objek yang sama.
Mengapa string bukan tipe nilai saja?
c#
string
clr
value-type
reference-type
Davy8
sumber
sumber
is
mengesampingkan tes), jawabannya mungkin "karena alasan historis". Kinerja penyalinan tidak dapat menjadi alasan karena tidak perlu menyalin objek yang tidak dapat diubah secara fisik. Sekarang tidak mungkin untuk mengubah tanpa melanggar kode yang benar-benar menggunakanis
cek (atau kendala serupa).std::string
berperilaku seperti koleksi adalah kesalahan lama yang tidak dapat diperbaiki sekarang.Jawaban:
String bukan tipe nilai karena bisa sangat besar, dan harus disimpan di heap. Jenis nilai (dalam semua implementasi CLR sampai saat ini) disimpan di stack. String pengalokasian tumpukan akan memecah segala hal: tumpukan hanya 1MB untuk 32-bit dan 4MB untuk 64-bit, Anda harus mem-box setiap string, menimbulkan penalti penyalinan, Anda tidak bisa mengintip string, dan penggunaan memori akan balon, dll ...
(Sunting: Klarifikasi tambahan tentang penyimpanan tipe nilai menjadi detail implementasi, yang mengarah ke situasi ini di mana kami memiliki tipe dengan semantik nilai yang tidak diwarisi dari System.ValueType. Terima kasih Ben.)
sumber
String
bukan ukuran variabel. Ketika Anda menambahkannya, Anda sebenarnya membuatString
objek lain , mengalokasikan memori baru untuknya.Int32
selalu 4 byte, sehingga kompiler mengalokasikan 4 byte setiap kali Anda mendefinisikan variabel string. Berapa banyak memori yang harus dialokasikan oleh kompiler ketika menemukanint
variabel (jika itu tipe nilai)? Pahamilah bahwa nilainya belum ditetapkan pada saat itu.Int32
selalu 4 byte, sehingga kompiler mengalokasikan 4 byte setiap kali Anda mendefinisikanint
variabel. Berapa banyak memori yang harus dialokasikan oleh kompiler ketika menemukanstring
variabel (jika itu tipe nilai)? Pahamilah bahwa nilainya belum ditetapkan pada saat itu.Ini bukan tipe nilai karena kinerja (ruang dan waktu!) Akan mengerikan jika itu tipe nilai dan nilainya harus disalin setiap kali dilewatkan ke dan dikembalikan dari metode, dll.
Ini memiliki nilai semantik untuk menjaga dunia tetap waras. Bisakah Anda bayangkan betapa sulitnya kode jika
diatur
b
menjadifalse
? Bayangkan betapa sulitnya menyandi hampir semua aplikasi.sumber
new String("foo");
- kadang dan yang lainnew String("foo")
dapat mengevaluasi dalam referensi yang sama, yang bukan yang Anda harapkan dilakukan olehnew
operator. (Atau dapatkah Anda memberi tahu saya sebuah kasus di mana saya ingin membandingkan referensi?)ReferenceEquals(x, y)
adalah tes cepat dan Anda dapat segera mengembalikan 0, dan ketika digabungkan dengan uji nol Anda bahkan tidak menambah pekerjaan lagi.string
bisa berperilaku sebagai string kosong (seperti pada sistem pre-.net) daripada sebagai referensi nol. Sebenarnya, preferensi saya sendiri adalah memiliki tipe nilaiString
yang berisi tipe referensiNullableString
, dengan yang pertama memiliki nilai default yang setara denganString.Empty
dan yang terakhir memiliki defaultnull
, dan dengan aturan tinju / unboxing khusus (seperti meninju default- dihargaiNullableString
akan menghasilkan referensi keString.Empty
).Perbedaan antara tipe referensi dan tipe nilai pada dasarnya adalah tradeoff kinerja dalam desain bahasa. Jenis referensi memiliki beberapa overhead pada konstruksi dan penghancuran dan pengumpulan sampah, karena mereka dibuat di heap. Tipe nilai di sisi lain memiliki overhead pada pemanggilan metode (jika ukuran data lebih besar dari pointer), karena seluruh objek disalin daripada hanya pointer. Karena string dapat (dan biasanya) jauh lebih besar dari ukuran pointer, mereka dirancang sebagai tipe referensi. Juga, seperti yang ditunjukkan Servy, ukuran tipe nilai harus diketahui pada waktu kompilasi, yang tidak selalu berlaku untuk string.
Pertanyaan tentang mutabilitas adalah masalah yang terpisah. Baik tipe referensi dan tipe nilai bisa berubah-ubah atau tidak berubah. Jenis nilai biasanya tidak berubah, karena semantik untuk jenis nilai yang dapat berubah dapat membingungkan.
Jenis referensi umumnya bisa berubah, tetapi dapat dirancang sebagai tidak berubah jika masuk akal. String didefinisikan sebagai tidak dapat diubah karena memungkinkan pengoptimalan tertentu. Misalnya, jika string literal yang sama muncul beberapa kali dalam program yang sama (yang cukup umum), kompiler dapat menggunakan kembali objek yang sama.
Jadi mengapa "==" kelebihan beban untuk membandingkan string dengan teks? Karena itu adalah semantik yang paling berguna. Jika dua string sama dengan teks, mereka mungkin atau mungkin tidak menjadi referensi objek yang sama karena optimasi. Jadi membandingkan referensi sama sekali tidak berguna, sementara membandingkan teks hampir selalu seperti yang Anda inginkan.
Berbicara lebih umum, Strings memiliki apa yang disebut semantik nilai . Ini adalah konsep yang lebih umum daripada tipe nilai, yang merupakan detail implementasi spesifik C #. Tipe nilai memiliki semantik nilai, tetapi tipe referensi juga memiliki semantik nilai. Ketika suatu tipe memiliki semantik nilai, Anda tidak dapat benar-benar mengetahui apakah implementasi yang mendasarinya adalah tipe referensi atau tipe nilai, sehingga Anda dapat mempertimbangkannya sebagai detail implementasi.
sumber
string
jenis akan perlu memiliki buffer char beberapa ukuran tetap, yang akan menjadi baik membatasi dan sangat tidak efisien.Ini adalah jawaban terlambat untuk pertanyaan lama, tetapi semua jawaban lain tidak ada gunanya, yaitu .NET tidak memiliki obat generik hingga .NET 2.0 pada 2005.
String
adalah tipe referensi bukan tipe nilai karena itu sangat penting bagi Microsoft untuk memastikan bahwa string dapat disimpan dengan cara yang paling efisien dalam koleksi non-generik , sepertiSystem.Collections.ArrayList
.Menyimpan tipe-nilai dalam koleksi non-generik membutuhkan konversi khusus ke tipe
object
yang disebut tinju. Ketika CLR mengotakkan tipe nilai, itu membungkus nilai di dalam aSystem.Object
dan menyimpannya di tumpukan yang dikelola.Membaca nilai dari koleksi membutuhkan operasi terbalik yang disebut unboxing.
Baik tinju maupun unboxing memiliki biaya yang tidak dapat diabaikan: tinju membutuhkan alokasi tambahan, unboxing membutuhkan pemeriksaan jenis.
Beberapa jawaban mengklaim secara keliru bahwa
string
tidak pernah dapat diimplementasikan sebagai tipe nilai karena ukurannya variabel. Sebenarnya mudah untuk menerapkan string sebagai struktur data panjang tetap menggunakan strategi Optimasi String Kecil: string akan disimpan dalam memori secara langsung sebagai urutan karakter Unicode kecuali untuk string besar yang akan disimpan sebagai pointer ke buffer eksternal. Kedua representasi dapat dirancang untuk memiliki panjang tetap yang sama, yaitu ukuran pointer.Jika generik sudah ada sejak hari pertama saya kira memiliki string sebagai tipe nilai mungkin akan menjadi solusi yang lebih baik, dengan semantik yang lebih sederhana, penggunaan memori yang lebih baik, dan lokalitas cache yang lebih baik. Hanya
List<string>
berisi string kecil bisa menjadi satu blok memori yang berdekatan.sumber
string
hanya berisi ukuran dan penunjuk kechar
array, jadi itu tidak akan menjadi "tipe nilai besar". Tapi ini adalah alasan sederhana dan relevan untuk keputusan desain ini. Terima kasih!Bukan hanya string yang merupakan tipe referensi yang tidak berubah. Delegasi multi-pemain juga. Itu sebabnya aman untuk menulis
Saya kira string tidak dapat diubah karena ini adalah metode paling aman untuk bekerja dengannya dan mengalokasikan memori. Mengapa mereka bukan tipe Nilai? Penulis sebelumnya benar tentang ukuran tumpukan dll. Saya juga akan menambahkan bahwa membuat string sebagai jenis referensi memungkinkan untuk menghemat ukuran perakitan ketika Anda menggunakan string konstan yang sama dalam program ini. Jika Anda mendefinisikan
Kemungkinannya adalah bahwa kedua instance dari konstanta "string saya" akan dialokasikan hanya sekali dalam perakitan Anda.
Jika Anda ingin mengelola string seperti tipe referensi biasa, masukkan string ke dalam StringBuilder baru (string s). Atau gunakan MemoryStreams.
Jika Anda ingin membuat pustaka, tempat Anda mengharapkan string besar untuk diteruskan dalam fungsi Anda, baik menentukan parameter sebagai StringBuilder atau sebagai Stream.
sumber
Juga, cara string diimplementasikan (berbeda untuk setiap platform) dan ketika Anda mulai menjahitnya bersama-sama. Suka menggunakan a
StringBuilder
. Ini mengalokasikan buffer untuk Anda salin ke, setelah Anda mencapai akhir, itu mengalokasikan lebih banyak memori untuk Anda, dengan harapan bahwa jika Anda melakukan kinerja penggabungan besar tidak akan terhalang.Mungkin Jon Skeet dapat membantu di sini?
sumber
Ini terutama masalah kinerja.
Memiliki string berperilaku SEPERTI nilai nilai membantu ketika menulis kode, tetapi memilikinya BE jenis nilai akan membuat hit kinerja besar.
Untuk tampilan yang lebih mendalam, lihat artikel yang bagus tentang string dalam kerangka .net.
sumber
Dengan kata yang sangat sederhana, nilai apa pun yang memiliki ukuran pasti dapat diperlakukan sebagai tipe nilai.
sumber
Bagaimana Anda tahu
string
jenis referensi? Saya tidak yakin itu penting bagaimana itu diterapkan. String dalam C # tidak dapat diubah dengan tepat sehingga Anda tidak perlu khawatir tentang masalah ini.sumber
Sebenarnya string memiliki sedikit kemiripan dengan tipe nilai. Sebagai permulaan, tidak semua tipe nilai tidak dapat diubah, Anda dapat mengubah nilai Int32 yang Anda inginkan dan itu akan tetap menjadi alamat yang sama pada stack.
String tidak dapat diubah karena alasan yang sangat bagus, string tidak ada hubungannya dengan itu menjadi tipe referensi, tetapi banyak hubungannya dengan manajemen memori. Ini hanya lebih efisien untuk membuat objek baru ketika ukuran string berubah daripada menggeser hal-hal di tumpukan terkelola. Saya pikir Anda mencampurkan bersama nilai / tipe referensi dan konsep objek yang tidak berubah.
Sejauh "==": Seperti yang Anda katakan "==" adalah kelebihan operator, dan sekali lagi itu diterapkan untuk alasan yang sangat baik untuk membuat kerangka kerja lebih berguna ketika bekerja dengan string.
sumber
Tidak sesederhana Strings yang terdiri dari array karakter. Saya melihat string sebagai array karakter []. Oleh karena itu mereka berada di heap karena lokasi memori referensi disimpan di stack dan menunjuk ke awal lokasi memori array di heap. Ukuran string tidak diketahui sebelum dialokasikan ... sempurna untuk heap.
Itulah sebabnya sebuah string benar-benar tidak dapat diubah karena ketika Anda mengubahnya walaupun ukurannya sama, kompiler tidak mengetahui hal itu dan harus mengalokasikan array baru dan menetapkan karakter ke posisi dalam array. Masuk akal jika Anda menganggap string sebagai cara bahasa melindungi Anda dari keharusan mengalokasikan memori dengan cepat (baca C seperti pemrograman)
sumber
Dengan risiko mendapat lagi suara misterius ... fakta bahwa banyak yang menyebutkan tumpukan dan memori sehubungan dengan tipe nilai dan tipe primitif adalah karena mereka harus masuk ke dalam register di mikroprosesor. Anda tidak dapat mendorong atau mengeluarkan sesuatu ke / dari tumpukan jika membutuhkan lebih banyak bit daripada register yang memiliki .... instruksinya adalah, misalnya "pop eax" - karena eax memiliki lebar 32 bit pada sistem 32-bit.
Tipe primitif floating-point ditangani oleh FPU, yang lebar 80 bit.
Ini semua diputuskan jauh sebelum ada bahasa OOP untuk mengaburkan definisi tipe primitif dan saya berasumsi bahwa tipe nilai adalah istilah yang telah dibuat khusus untuk bahasa OOP.
sumber