Mengapa nilai default dari tipe string null bukan string kosong?


218

Cukup menjengkelkan untuk menguji semua string saya nullsebelum saya dapat dengan aman menerapkan metode seperti ToUpper(), StartWith()dll ...

Jika nilai default stringadalah string kosong, saya tidak perlu menguji, dan saya akan merasa lebih konsisten dengan tipe nilai lainnya seperti intatau doublemisalnya. Selain itu Nullable<String>akan masuk akal.

Jadi mengapa para desainer C # memilih untuk menggunakan nullsebagai nilai default dari string?

Catatan: Ini berkaitan dengan pertanyaan ini , tetapi lebih fokus pada mengapa dan bukan apa yang harus dilakukan dengannya.


53
Apakah Anda menganggap ini masalah untuk jenis referensi lain ?
Jon Skeet

17
@ JonSkeet Tidak, tetapi hanya karena saya awalnya, salah, berpikir bahwa string adalah tipe nilai.
Marcel

21
@ Marscel: Itu alasan yang cukup bagus untuk bertanya-tanya tentang hal itu.
TJ Crowder

7
@JonSkeet Ya. Oh ya. (Tapi Anda tidak asing dengan diskusi jenis referensi yang tidak dapat dibatalkan ...)
Konrad Rudolph

7
Saya percaya Anda akan memiliki waktu yang jauh lebih baik jika Anda menggunakan pernyataan pada string Anda di tempat-tempat di mana Anda tidak mengharapkannya null(dan juga saya sarankan Anda memperlakukan nulldan mengosongkan string secara konseptual sebagai hal yang berbeda). Nilai nol bisa merupakan hasil dari kesalahan di suatu tempat, sementara string kosong harus menyampaikan arti yang berbeda.
diegoreymendez

Jawaban:


312

Mengapa nilai default dari tipe string null bukan string kosong?

Karena stringadalah tipe referensi dan nilai default untuk semua tipe referensi adalah null.

Cukup menjengkelkan untuk menguji semua string saya untuk null sebelum saya dapat dengan aman menerapkan metode seperti ToUpper (), StartWith () dll ...

Itu konsisten dengan perilaku tipe referensi. Sebelum memohon anggota contoh mereka, seseorang harus meletakkan cek di tempat untuk referensi nol.

Jika nilai default dari string adalah string kosong, saya tidak perlu menguji, dan saya akan merasa itu lebih konsisten dengan tipe nilai lainnya seperti int atau double misalnya.

Menetapkan nilai default ke jenis referensi tertentu selain nullakan membuatnya tidak konsisten .

Selain itu Nullable<String>akan masuk akal.

Nullable<T>bekerja dengan tipe nilai. Yang perlu diperhatikan adalah fakta yang Nullabletidak diperkenalkan pada platform .NET asli sehingga akan ada banyak kode yang rusak seandainya mereka mengubah aturan itu. ( Courtesy @jcolebrand )


10
@HenkHolterman One dapat menerapkan banyak hal, tetapi mengapa memperkenalkan inkonsistensi yang mencolok seperti itu?

4
@delnan - "mengapa" adalah pertanyaan di sini.
Henk Holterman

8
@HenkHolterman Dan "Konsistensi" adalah sanggahan untuk poin Anda "string dapat diperlakukan tidak seperti jenis referensi lain".

6
@delnan: Sedang mengerjakan bahasa yang memperlakukan string sebagai tipe nilai dan bekerja 2+ tahun di dotnet, saya setuju dengan Henk. Saya melihatnya sebagai cacat utama pada dotnet.
Fabricio Araujo

1
@delnan: Satu dapat membuat jenis nilai yang berperilaku dasarnya seperti String, kecuali untuk (1) perilaku nilai jenis-ish-memiliki nilai yang dapat digunakan default, dan (2) tambahan lapisan disayangkan tipuan tinju waktu itu dilemparkan ke Object. Mengingat bahwa penumpukan tumpukan stringadalah unik, memiliki perawatan khusus untuk menghindari tinju tambahan tidak akan terlalu banyak (sebenarnya, mampu menentukan perilaku tinju non-standar akan menjadi hal yang baik untuk jenis lain juga).
supercat

40

Habib benar - karena stringmerupakan jenis referensi.

Tetapi yang lebih penting, Anda tidak perlu memeriksa nullsetiap kali Anda menggunakannya. Anda mungkin harus melempar ArgumentNullExceptionjika seseorang melewati fungsi Anda sebagai nullreferensi.

Begini masalahnya - kerangka kerja akan melempar NullReferenceExceptionuntuk Anda jika Anda mencoba memanggil .ToUpper()string. Ingatlah bahwa kasus ini masih dapat terjadi bahkan jika Anda menguji argumen Anda nullkarena properti atau metode apa pun pada objek yang diteruskan ke fungsi Anda sebagai parameter dapat dievaluasi null.

Yang sedang berkata, memeriksa string kosong atau nol adalah hal yang umum dilakukan, sehingga mereka menyediakan String.IsNullOrEmpty()dan String.IsNullOrWhiteSpace()hanya untuk tujuan ini.


30
Anda tidak boleh melempar NullReferenceExceptiondiri Anda sendiri ( msdn.microsoft.com/en-us/library/ms173163.aspx ); Anda melempar ArgumentNullExceptionjika metode Anda tidak dapat menerima referensi kosong. Juga, NullRef biasanya merupakan salah satu pengecualian yang lebih sulit untuk diagnosa ketika Anda memperbaiki masalah, jadi saya tidak berpikir rekomendasi untuk tidak memeriksa null adalah yang sangat bagus.
Andy

3
@Andy "NullRef biasanya merupakan salah satu pengecualian yang paling sulit untuk didiagnosis" Saya sangat tidak setuju, jika Anda mencatat barang, itu benar-benar mudah ditemukan & diperbaiki (hanya menangani kasus nol).
Louis Kottmann

6
Pelemparan ArgumentNullException memiliki manfaat tambahan karena bisa memberikan nama parameter. Selama debugging, ini menghemat ... err, detik. Tapi detik-detik penting.
Kos

2
@DaveMarkle Anda mungkin ingin memasukkan IsNullOrWhitespace juga msdn.microsoft.com/en-us/library/…
Nathan Koop

1
Saya benar-benar berpikir memeriksa nol di mana-mana adalah sumber kode besar sekali. itu jelek, dan terlihat berantakan dan sulit untuk tetap konsisten. Saya pikir (setidaknya dalam bahasa C #-seperti) aturan yang baik adalah "larangan kata kunci nol dalam kode produksi, gunakan seperti gila dalam kode uji".
sara

24

Anda dapat menulis metode ekstensi (sesuai nilainya):

public static string EmptyNull(this string str)
{
    return str ?? "";
}

Sekarang ini bekerja dengan aman:

string str = null;
string upper = str.EmptyNull().ToUpper();

100
Tapi tolong jangan. Hal terakhir yang ingin dilihat oleh programmer lain adalah ribuan baris kode dibumbui dengan .EmptyNull () di mana-mana hanya karena orang pertama itu "takut" terhadap pengecualian.
Dave Markle

15
@ DaveMarkle: Tapi jelas itu yang OP cari. "Cukup menjengkelkan untuk menguji semua string saya untuk null sebelum saya dapat dengan aman menerapkan metode seperti ToUpper (), StartWith () dll"
Tim Schmelter

19
Komentar untuk OP, bukan untuk Anda. Meskipun jawaban Anda jelas benar, seorang programmer yang mengajukan pertanyaan mendasar seperti ini harus sangat berhati-hati agar tidak benar-benar memasukkan solusi Anda ke dalam praktik WIDE, seperti yang sering terjadi pada mereka. Ada sejumlah pengorbanan yang tidak Anda diskusikan dalam jawaban Anda, seperti kekaburan, peningkatan kompleksitas, kesulitan refactoring, potensi penggunaan metode ekstensi yang berlebihan, dan ya, kinerja. Terkadang (berkali-kali) jawaban yang benar bukanlah jalan yang benar, dan inilah sebabnya saya berkomentar.
Dave Markle

5
@Andy: Solusi untuk tidak melakukan pemeriksaan null yang benar dilakukan adalah dengan memeriksa nulls dengan benar, bukan dengan menempatkan bantuan band pada masalah.
Dave Markle

7
Jika Anda mengalami kesulitan menulis .EmptyNull(), mengapa tidak menggunakan (str ?? "")saja di tempat yang dibutuhkan? Yang mengatakan, saya setuju dengan sentimen yang diungkapkan dalam komentar @ DaveMarkle: Anda mungkin tidak boleh. nulldan String.Emptysecara konsep berbeda, dan Anda tidak dapat memperlakukan satu sama seperti yang lainnya.
CVn

17

Anda juga dapat menggunakan yang berikut ini, pada C # 6.0

string myString = null;
string result = myString?.ToUpper();

Hasil string akan menjadi nol.


1
Benar, sejak c # 6.0, versi IDE tidak ada hubungannya dengan ini karena ini adalah fitur bahasa.
Stijn Van Antwerpen

3
Opsi lain -public string Name { get; set; } = string.Empty;
Jaja Harris

Apa ini namanya? myString? .ToUpper ();
Hunter Nelson

1
Ini disebut Operator Bersyarat Null. Anda dapat membacanya di sini msdn.microsoft.com/en-us/magazine/dn802602.aspx
russelrillema

14

String kosong dan null pada dasarnya berbeda. Null adalah tidak adanya nilai dan string kosong adalah nilai yang kosong.

Bahasa pemrograman membuat asumsi tentang "nilai" suatu variabel, dalam hal ini string kosong, akan sama baiknya dengan menginisiasi string dengan nilai lain yang tidak akan menyebabkan masalah referensi nol.

Juga, jika Anda meneruskan pegangan ke variabel string itu ke bagian lain dari aplikasi, maka kode itu tidak akan memiliki cara untuk memvalidasi apakah Anda sengaja memberikan nilai kosong atau Anda lupa mengisi nilai variabel itu.

Kesempatan lain di mana ini akan menjadi masalah adalah ketika string adalah nilai balik dari beberapa fungsi. Karena string adalah tipe referensi dan secara teknis dapat memiliki nilai sebagai nol dan kosong keduanya, maka fungsi tersebut juga secara teknis dapat mengembalikan nol atau kosong (tidak ada yang menghentikannya untuk melakukannya). Sekarang, karena ada 2 gagasan tentang "tidak adanya nilai", yaitu string kosong dan null, semua kode yang menggunakan fungsi ini harus melakukan 2 pemeriksaan. Satu untuk kosong dan yang lainnya untuk nol.

Singkatnya, selalu baik untuk hanya memiliki 1 representasi untuk satu negara. Untuk diskusi yang lebih luas tentang kosong dan kosong, lihat tautan di bawah ini.

/software/32578/sql-empty-string-vs-null-value

NULL vs Kosong saat berurusan dengan input pengguna


2
Dan bagaimana tepatnya Anda melihat perbedaan ini, katakanlah di kotak teks? Apakah pengguna lupa memasukkan nilai di bidang, atau mereka sengaja membiarkannya kosong? Null dalam bahasa pemrograman memang memiliki arti tertentu; tidak ditugaskan. Kami tahu itu tidak memiliki nilai, yang tidak sama dengan nol basis data.
Andy

1
tidak ada banyak perbedaan ketika Anda menggunakannya dengan kotak teks. Either way, memiliki satu notasi untuk mewakili tidak adanya nilai dalam suatu string adalah yang terpenting. Jika saya harus memilih satu, saya akan memilih nol.
Nerrve

Dalam Delphi, string adalah tipe nilai dan karenanya tidak boleh nol. Ini membuat hidup jauh lebih mudah dalam hal ini - saya benar-benar merasa sangat menjengkelkan menjadikan string sebagai tipe referensi.
Fabricio Araujo

1
Di bawah COM (Common Object Model) yang mendahului .net, tipe string akan menahan pointer ke data string, atau nulluntuk mewakili string kosong. Ada beberapa cara .net dapat menerapkan semantik serupa, seandainya mereka memilih untuk melakukannya, terutama mengingat bahwa Stringmemiliki sejumlah karakteristik yang menjadikannya tipe yang unik [misalnya dan dua tipe array adalah satu-satunya jenis yang alokasi ukuran tidak konstan].
supercat

7

Alasan / masalah mendasar adalah bahwa perancang spesifikasi CLS (yang mendefinisikan bagaimana bahasa berinteraksi dengan .net) tidak mendefinisikan sarana yang oleh anggota kelas dapat menentukan bahwa mereka harus dipanggil secara langsung, bukan melaluicallvirt , tanpa penelepon melakukan suatu cek referensi-nol; juga tidak memberikan banyak definisi struktur yang tidak akan dikenakan tinju "normal".

Jika spesifikasi CLS mendefinisikan cara seperti itu, maka akan mungkin bagi .net untuk secara konsisten mengikuti petunjuk yang ditetapkan oleh Common Object Model (COM), di mana referensi string nol dianggap secara semantik setara dengan string kosong, dan untuk lainnya tipe kelas yang tidak dapat diubah yang ditentukan pengguna yang seharusnya memiliki semantik nilai untuk juga mendefinisikan nilai default. Pada dasarnya, apa yang akan terjadi bagi masing-masing anggota String, misalnya Lengthditulis sebagai sesuatu seperti [InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }. Pendekatan ini akan menawarkan semantik yang sangat bagus untuk hal-hal yang harus berperilaku seperti nilai, tetapi karena masalah implementasi perlu disimpan di heap. Kesulitan terbesar dengan pendekatan ini adalah bahwa semantik konversi antara jenis danObject bisa menjadi sedikit keruh.

Suatu pendekatan alternatif akan memungkinkan definisi tipe struktur khusus yang tidak diwarisi dari Objecttetapi memiliki operasi tinju kustom dan unboxing (yang akan dikonversi ke / dari beberapa jenis kelas lainnya). Di bawah pendekatan seperti itu, akan ada tipe kelas NullableStringyang berperilaku sebagai string tidak sekarang, dan tipe struct kotak-kustom String, yang akan menampung bidang Valuetipe privat tunggal String. Mencoba mengonversikan Stringke NullableStringatau Objectakan kembali Valuejika bukan nol, atau String.Emptyjika nol. Mencoba melakukan cast String, referensi non-nol ke NullableStringinstance akan menyimpan referensi di Value(mungkin menyimpan null jika panjangnya nol); casting referensi lain akan menimbulkan pengecualian.

Meskipun string harus disimpan di heap, secara konseptual tidak ada alasan mengapa mereka tidak boleh berperilaku seperti tipe nilai yang memiliki nilai default non-nol. Memiliki mereka disimpan sebagai struktur "normal" yang memegang referensi akan lebih efisien untuk kode yang menggunakannya sebagai tipe "string", tetapi akan menambahkan lapisan tipuan dan inefisiensi tambahan saat casting ke "objek". Meskipun saya tidak melihat .net menambahkan salah satu dari fitur di atas pada akhir ini, mungkin desainer kerangka masa depan mungkin mempertimbangkan untuk memasukkannya.


1
Berbicara sebagai seseorang yang banyak bekerja dalam SQL, dan telah berurusan dengan sakit kepala Oracle yang tidak membuat perbedaan antara NULL dan nol panjang, saya sangat senang bahwa .NET melakukannya . "Kosong" adalah sebuah nilai, "null" bukan.

@JonofAllTrades: Saya tidak setuju. Pada kode aplikasi, kecuali berurusan dengan kode db, tidak ada artinya string diperlakukan sebagai kelas. Ini adalah tipe nilai dan dasar. Supercat: +1 untuk Anda
Fabricio Araujo

1
Kode basis data adalah "kecuali" yang besar. Selama ada beberapa domain masalah di mana Anda perlu membedakan antara "sekarang / dikenal, string kosong" dan "tidak ada / tidak diketahui / tidak dapat diterapkan", seperti database, maka bahasa perlu mendukungnya. Tentu saja sekarang. NET memiliki Nullable<>, string dapat diimplementasikan kembali sebagai tipe nilai; Saya tidak dapat berbicara mengenai biaya dan manfaat dari pilihan semacam itu.

3
@JonofAllTrades: Kode yang berhubungan dengan angka harus memiliki sarana out-of-band untuk membedakan nilai default nol dari "tidak terdefinisi". Karena itu, kode penanganan nullable yang berfungsi dengan string dan angka harus menggunakan satu metode untuk string yang dapat nullable dan yang lainnya untuk angka nullable. Bahkan jika tipe kelas nullable stringlebih efisien daripada yang Nullable<string>seharusnya, harus menggunakan metode "lebih efisien" lebih memberatkan daripada mampu menggunakan pendekatan yang sama untuk semua nilai basis data data yang dapat dibatalkan.
supercat

5

Karena variabel string adalah referensi , bukan instance .

Inisialisasi ke Empty secara default mungkin saja tetapi akan memperkenalkan banyak inkonsistensi di seluruh papan.


3
Tidak ada alasan khusus stringuntuk menjadi tipe referensi. Yang pasti, karakter aktual yang membentuk string tentu saja harus disimpan di heap, tetapi mengingat jumlah dukungan khusus yang dimiliki string di CLR, tidak akan sulit untuk System.Stringmenjadi tipe nilai dengan bidang Valuetipe tunggal pribadi HeapString. Bidang itu akan menjadi tipe referensi, dan akan default untuk null, tetapi Stringstruct yang Valuebidangnya nol akan berperilaku sebagai string kosong. Satu-satunya kelemahan dari pendekatan ini adalah ...
supercat

1
... yang melakukan casting Stringke Object, tanpa adanya kode kasus khusus dalam runtime, menyebabkan pembuatan Stringinstance kotak pada heap, daripada hanya menyalin referensi ke HeapString.
supercat

1
@supercat - tidak ada yang mengatakan bahwa string harus / bisa menjadi tipe nilai.
Henk Holterman

1
Tidak ada yang kecuali saya. Memiliki string menjadi tipe nilai "khusus" (dengan bidang tipe referensi pribadi) akan memungkinkan sebagian besar penanganan menjadi seefisien sekarang, kecuali untuk pemeriksaan nol tambahan pada metode / properti seperti .Lengthdll. Sehingga instance yang menyimpan referensi nol tidak akan mencoba untuk menghindarinya tetapi sebaliknya berperilaku sesuai untuk string kosong. Apakah Kerangka akan lebih baik atau lebih buruk dengan stringdiimplementasikan seperti itu, jika seseorang ingin default(string)menjadi string kosong ...
supercat

1
... memiliki stringpembungkus tipe nilai pada bidang tipe referensi akan menjadi pendekatan yang memerlukan perubahan paling sedikit ke bagian lain dari .net [memang, jika seseorang bersedia menerima konversi dari Stringuntuk Objectmembuat item kotak tambahan, seseorang bisa saja Stringmenjadi struct biasa dengan bidang tipe Char[]yang tidak pernah terpapar]. Saya pikir memiliki HeapStringtipe mungkin akan lebih baik, tetapi dalam beberapa hal memegang tipe-nilai string Char[]akan lebih sederhana.
supercat

5

Mengapa perancang C # memilih untuk menggunakan null sebagai nilai default dari string?

Karena string adalah tipe referensi, tipe referensi adalah nilai defaultnyanull . Variabel tipe referensi menyimpan referensi ke data aktual.

Mari kita gunakan defaultkata kunci untuk kasus ini;

string str = default(string); 

stradalah string, jadi itu adalah tipe referensi , jadi nilai default adalah null.

int str = (default)(int);

stradalah int, jadi itu adalah tipe nilai , jadi nilai default adalah zero.


4

Jika nilai default stringadalah string kosong, saya tidak perlu menguji

Salah! Mengubah nilai default tidak mengubah fakta bahwa itu adalah tipe referensi dan seseorang masih dapat secara eksplisit mengatur referensi menjadi null.

Selain itu Nullable<String>akan masuk akal.

Titik benar. Akan lebih masuk akal untuk tidak mengizinkan nulljenis referensi apa pun, alih-alih memerlukan Nullable<TheRefType>fitur itu.

Jadi mengapa para desainer C # memilih untuk menggunakan nullsebagai nilai default dari string?

Konsistensi dengan jenis referensi lain. Sekarang, mengapa mengizinkan nulltipe referensi sama sekali? Mungkin sehingga terasa seperti C, meskipun ini adalah keputusan desain yang dipertanyakan dalam bahasa yang juga menyediakan Nullable.


3
Mungkin karena Nullable hanya diperkenalkan di .NET 2.0 Framework, jadi sebelum itu tidak tersedia?
jcolebrand

3
Terima kasih Dan Burton untuk menunjukkan bahwa seseorang DAPAT menetapkan nilai yang diinisialisasi ke nol pada tipe referensi nanti. Memikirkan hal ini memberi tahu saya bahwa maksud asli saya dalam pertanyaan itu tidak berguna.
Marcel

4

Mungkin jika Anda akan menggunakan ??operator saat menetapkan variabel string Anda, ini mungkin membantu Anda.

string str = SomeMethodThatReturnsaString() ?? "";
// if SomeMethodThatReturnsaString() returns a null value, "" is assigned to str.

2

String adalah objek abadi yang berarti ketika diberi nilai, nilai lama tidak dihapus dari memori, tetapi tetap berada di lokasi lama, dan nilai baru diletakkan di lokasi baru. Jadi jika nilai defaultnya String aadalah String.Empty, itu akan membuang - buangString.Empty blok dalam memori ketika diberi nilai pertama.

Meskipun tampaknya sangat kecil, itu bisa berubah menjadi masalah ketika menginisialisasi sejumlah besar string dengan nilai default String.Empty. Tentu saja, Anda selalu bisa menggunakan StringBuilderkelas yang bisa berubah jika ini akan menjadi masalah.


Terima kasih telah menyebutkan hal "inisialisasi pertama".
Marcel

3
Bagaimana itu menjadi masalah saat menginisialisasi array besar? Karena, seperti yang Anda katakan, String tidak dapat diubah, semua elemen array hanya akan menjadi pointer ke hal yang sama String.Empty. Apakah saya salah?
Dan Burton

2
Nilai default untuk semua jenis akan memiliki semua bit diatur ke nol. Satu-satunya cara untuk nilai default stringmenjadi string kosong adalah dengan mengizinkan semua-bit-nol sebagai representasi dari string kosong. Ada beberapa cara ini bisa dilakukan, tetapi saya tidak berpikir ada melibatkan inisialisasi referensi String.Empty.
supercat

Jawaban lain juga membahas hal ini. Saya pikir orang telah menyimpulkan bahwa tidak masuk akal untuk memperlakukan kelas String sebagai kasus khusus dan memberikan sesuatu selain all-bits-zero sebagai inisialisasi, bahkan jika itu seperti String.Emptyatau "".
djv

@DanV: Mengubah perilaku inisialisasi stringlokasi penyimpanan akan diperlukan juga mengubah perilaku inisialisasi semua struct atau kelas yang memiliki bidang tipe string. Itu akan mewakili perubahan yang cukup besar dalam desain .net, yang saat ini mengharapkan untuk menginisialisasi-nol jenis apa pun tanpa harus memikirkan apa itu, simpan hanya untuk ukuran totalnya.
supercat

2

Karena string adalah tipe referensi dan nilai default untuk tipe referensi adalah nol.


0

Mungkin stringkata kunci membingungkan Anda, karena terlihat persis seperti pernyataan tipe nilai lainnya , tetapi sebenarnya merupakan alias untuk System.Stringseperti yang dijelaskan dalam pertanyaan ini .
Juga warna biru gelap di Visual Studio dan huruf kecil pertama mungkin menyesatkan untuk berpikir itu adalah struct.


3
Bukankah hal yang sama berlaku untuk objectkata kunci? Meskipun diakui, itu jauh lebih sedikit digunakan daripada string.

2
As intadalah alias untuk System.Int32. Apa maksudmu :)
Thorarin

@Thorari @delnan: Mereka berdua alias, tapi System.Int32adalah Structsehingga memiliki nilai default sementara System.Stringadalah Classmemiliki pointer dengan nilai default null. Mereka disajikan secara visual dalam font / warna yang sama. Tanpa pengetahuan, orang bisa berpikir mereka bertindak dengan cara yang sama (= memiliki nilai default). Jawaban saya ditulis dengan en.wikipedia.org/wiki/Cognitive_psychology ide psikologi kognitif di belakangnya :-)
Alessandro Da Rugna

Saya cukup yakin Anders Hejlsberg yang mengatakannya dalam saluran 9 wawancara. Saya tahu perbedaan antara tumpukan dan tumpukan tetapi gagasan dengan C # adalah bahwa programmer biasa tidak perlu.
Thomas Koelle

0

Jenis nullable tidak masuk sampai 2.0.

Jika jenis nullable telah dibuat di awal bahasa maka string tidak akan nullable dan string? akan dibatalkan. Tetapi mereka tidak bisa melakukan kompatibilitas mundur ini.

Banyak orang berbicara tentang tipe-ref atau bukan tipe ref, tetapi string adalah kelas yang tidak biasa dan solusi telah ditemukan untuk membuatnya mungkin.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.