Tipe referensi string C #?

163

Saya tahu bahwa "string" dalam C # adalah tipe referensi. Ini di MSDN. Namun, kode ini tidak berfungsi sebagaimana mestinya:

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}

Keluaran harus "sebelum meneruskan" "setelah lewat" karena saya meneruskan string sebagai parameter dan itu menjadi tipe referensi, pernyataan keluaran kedua harus mengenali bahwa teks berubah dalam metode TestI. Namun, saya mendapatkan "sebelum lewat" "sebelum lewat" membuatnya seolah-olah nilai itu diteruskan oleh nilai bukan oleh ref. Saya mengerti bahwa string tidak dapat diubah, tetapi saya tidak melihat bagaimana hal itu akan menjelaskan apa yang sedang terjadi di sini. Apa yang saya lewatkan? Terima kasih.


sumber
Lihat artikel yang dirujuk oleh Jon di bawah ini. Perilaku yang Anda sebutkan dapat direproduksi oleh pointer C ++ juga.
Sesh
Penjelasan yang sangat bagus di MSDN juga.
Dimi_Pel

Jawaban:

211

Referensi ke string dilewatkan oleh nilai. Ada perbedaan besar antara melewatkan referensi dengan nilai dan melewati objek dengan referensi. Sangat disayangkan bahwa kata "referensi" digunakan dalam kedua kasus.

Jika Anda melakukan lulus string referensi oleh referensi, ia akan bekerja seperti yang Anda harapkan:

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}

Sekarang Anda perlu membedakan antara membuat perubahan pada objek yang dirujuk oleh referensi, dan membuat perubahan pada variabel (seperti parameter) untuk membiarkannya merujuk ke objek yang berbeda. Kami tidak dapat membuat perubahan pada string karena string tidak dapat diubah, tetapi kami dapat mendemonstrasikannya dengan StringBuilder:

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}

Lihat artikel saya tentang parameter yang lewat untuk detail lebih lanjut.

Jon Skeet
sumber
2
setuju, hanya ingin menjelaskan bahwa menggunakan pengubah ref juga berfungsi untuk tipe non-referensi yaitu keduanya adalah konsep yang cukup terpisah.
eglasius
2
@ Jon Skeet menyukai sidenote di artikel Anda. Anda seharusnya referencedmenjawabnya
Nithish Inpursuit Ofhappiness
36

Jika kita harus menjawab pertanyaan: String adalah tipe referensi dan berperilaku sebagai referensi. Kami melewati parameter yang menyimpan referensi, bukan string yang sebenarnya. Masalahnya ada di fungsi:

public static void TestI(string test)
{
    test = "after passing";
}

Parameter testmenyimpan referensi ke string tetapi itu adalah salinan. Kami memiliki dua variabel yang menunjuk ke string. Dan karena operasi apa pun dengan string benar-benar membuat objek baru, kami membuat salinan lokal kami untuk menunjuk ke string baru. Tetapi testvariabel asli tidak berubah.

Solusi yang disarankan untuk dimasukkan ke refdalam deklarasi fungsi dan dalam pekerjaan pemanggilan karena kita tidak akan melewatkan nilai testvariabel tetapi hanya akan meneruskan referensi ke sana. Jadi setiap perubahan di dalam fungsi akan mencerminkan variabel asli.

Saya ingin mengulangi di akhir: String adalah tipe referensi tetapi karena tidak berubah baris test = "after passing";sebenarnya menciptakan objek baru dan salinan variabel kami testdiubah untuk menunjuk ke string baru.

Martin Dimitrov
sumber
25

Seperti yang telah dinyatakan oleh orang lain, Stringtipe dalam. NET tidak dapat diubah dan rujukannya dilewatkan oleh nilai.

Dalam kode asli, segera setelah baris ini dijalankan:

test = "after passing";

maka testtidak lagi mengacu pada objek asli. Kami telah membuat objek baru String dan ditugaskan testuntuk referensi objek itu di tumpukan yang dikelola.

Saya merasa bahwa banyak orang tersandung di sini karena tidak ada konstruktor formal yang terlihat untuk mengingatkan mereka. Dalam hal ini, itu terjadi di belakang layar sejakString jenisnya memiliki dukungan bahasa dalam cara membangunnya.

Karenanya, inilah mengapa perubahan menjadi testtidak terlihat di luar cakupan TestI(string)metode - kami telah melewati referensi berdasarkan nilai dan sekarang nilai tersebut telah berubah! Tetapi jika Stringreferensi disahkan oleh referensi, maka ketika referensi berubah kita akan melihatnya di luar ruang lingkup TestI(string)metode.

Entah ref atau keluar kata kunci yang diperlukan dalam hal ini. Saya merasa outkata kunci mungkin sedikit lebih cocok untuk situasi khusus ini.

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}
Derek W
sumber
ref = inisialisasi fungsi luar, out = inisialisasi fungsi, atau dengan kata lain; ref adalah dua arah, keluar hanya keluar. Jadi pasti ref harus digunakan.
Paul Zahra
@ PaulZahra: outperlu ditetapkan dalam metode untuk dikompilasi kode. reftidak memiliki persyaratan seperti itu. Juga outparameter diinisialisasi luar metode - kode dalam jawaban ini adalah counterexample.
Derek W
Haruskah klarifikasi - outparameter dapat diinisialisasi di luar metode, tetapi tidak harus. Dalam hal ini, kami ingin menginisialisasi outparameter untuk menunjukkan titik tentang sifat stringtipe .NET.
Derek W
9

Sebenarnya itu akan sama untuk objek apa pun dalam hal ini yaitu menjadi tipe referensi dan lewat referensi adalah 2 hal berbeda dalam c #.

Ini akan berhasil, tetapi itu berlaku terlepas dari jenis:

public static void TestI(ref string test)

Juga tentang string yang menjadi tipe referensi, itu juga yang spesial. Ini dirancang agar tidak berubah, sehingga semua metode tidak akan mengubah instance (mereka mengembalikan yang baru). Ini juga memiliki beberapa hal tambahan untuk kinerja.

eglasius
sumber
7

Berikut cara yang baik untuk memikirkan perbedaan antara tipe-nilai, passing-by-value, tipe-referensi, dan passing-oleh-referensi:

Variabel adalah wadah.

Variabel tipe nilai berisi instance. Variabel tipe referensi berisi pointer ke instance yang disimpan di tempat lain.

Memodifikasi variabel tipe-nilai bermutasi instance yang dikandungnya. Memodifikasi variabel tipe referensi bermutasi contoh yang menunjuk.

Variabel tipe referensi yang terpisah dapat menunjuk ke instance yang sama. Oleh karena itu, instance yang sama dapat dimutasi melalui variabel apa pun yang menunjuk ke sana.

Argumen lewat-nilai adalah wadah baru dengan salinan konten yang baru. Argumen lulus-oleh-referensi adalah wadah asli dengan konten aslinya.

Ketika argumen tipe nilai diteruskan oleh nilai: Menugaskan kembali konten argumen tidak berpengaruh di luar cakupan, karena wadah itu unik. Mengubah argumen tidak berpengaruh di luar cakupan, karena instance adalah salinan independen.

Ketika argumen tipe referensi diteruskan oleh nilai: Menugaskan kembali konten argumen tidak memiliki pengaruh di luar ruang lingkup, karena wadah itu unik. Memodifikasi konten argumen memengaruhi lingkup eksternal, karena penunjuk yang disalin menunjuk ke instance bersama.

Ketika ada argumen yang dilewatkan oleh referensi: Menugaskan kembali konten argumen mempengaruhi ruang lingkup eksternal, karena wadah dibagikan. Memodifikasi konten argumen memengaruhi lingkup eksternal, karena konten dibagikan.

Kesimpulannya:

Variabel string adalah variabel tipe referensi. Oleh karena itu, ini berisi pointer ke instance yang disimpan di tempat lain. Ketika diteruskan oleh nilai, penunjuknya disalin, jadi memodifikasi argumen string harus memengaruhi instance yang dibagikan. Namun, turunan string tidak memiliki sifat yang dapat diubah, sehingga argumen string tidak dapat dimodifikasi. Ketika lulus-oleh-referensi, wadah pointer dibagikan, sehingga penugasan kembali masih akan mempengaruhi lingkup eksternal.

Bryan
sumber
6

" Sebuah gambar bernilai ribuan kata ".

Saya punya contoh sederhana di sini, mirip dengan kasus Anda.

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc

Inilah yang terjadi:

masukkan deskripsi gambar di sini

  • Baris 1 dan 2: s1dan s2referensi variabel sama"abc" objek string yang .
  • Baris 3: Karena string tidak dapat diubah , maka "abc"objek string tidak memodifikasi sendiri (to "def"), tetapi "def"objek string baru dibuat sebagai gantinya, dan kemudians1 merujuknya.
  • Baris 4: s2masih merujuk ke "abc"objek string, jadi itulah outputnya.
Messi
sumber
5

Jawaban di atas sangat membantu, saya hanya ingin menambahkan contoh yang saya pikir menunjukkan dengan jelas apa yang terjadi ketika kita melewati parameter tanpa kata kunci ref, bahkan ketika parameter itu adalah tipe referensi:

MyClass c = new MyClass(); c.MyProperty = "foo";

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar


private void CNull(MyClass c2)
        {          
            c2 = null;
        }
private void CPropertyChange(MyClass c2) 
        {
            c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
        }
BornToCode
sumber
1
Penjelasan ini cocok untuk saya. Jadi pada dasarnya kita melewatkan semuanya berdasarkan nilai meskipun faktanya variabel itu sendiri adalah nilai atau tipe referensi kecuali kita menggunakan kata kunci ref (atau keluar). Itu tidak menonjol dengan pengkodean hari ke hari kami karena kami biasanya tidak menetapkan objek kami ke nol atau ke contoh berbeda di dalam metode di mana mereka dilewatkan, melainkan kami mengatur properti mereka atau memanggil metode mereka. Dalam kasus "string", menyetelnya ke instance baru terjadi setiap saat tetapi yang baru tidak terlihat dan itu memberikan interpretasi yang salah ke mata yang tidak terlatih. Koreksi saya jika salah.
Ε Г И І И О
3

Untuk pikiran yang ingin tahu dan untuk menyelesaikan percakapan: Ya, String adalah tipe referensi :

unsafe
{
     string a = "Test";
     string b = a;
     fixed (char* p = a)
     {
          p[0] = 'B';
     }
     Console.WriteLine(a); // output: "Best"
     Console.WriteLine(b); // output: "Best"
}

Tetapi perhatikan bahwa perubahan ini hanya berfungsi di blok yang tidak aman ! karena String tidak dapat diubah (Dari MSDN):

Isi objek string tidak dapat diubah setelah objek dibuat, meskipun sintaks membuatnya tampak seolah-olah Anda bisa melakukan ini. Misalnya, ketika Anda menulis kode ini, kompiler sebenarnya membuat objek string baru untuk menampung urutan karakter baru, dan objek baru tersebut ditugaskan untuk b. String "h" kemudian memenuhi syarat untuk pengumpulan sampah.

string b = "h";  
b += "ello";  

Dan perlu diingat bahwa:

Meskipun string adalah tipe referensi, operator persamaan ( ==dan !=) didefinisikan untuk membandingkan nilai objek string, bukan referensi.

NY
sumber
0

Saya percaya kode Anda analog dengan yang berikut ini, dan Anda seharusnya tidak mengharapkan nilai telah berubah karena alasan yang sama tidak ada di sini:

 public static void Main()
 {
     StringWrapper testVariable = new StringWrapper("before passing");
     Console.WriteLine(testVariable);
     TestI(testVariable);
     Console.WriteLine(testVariable);
 }

 public static void TestI(StringWrapper testParameter)
 {
     testParameter = new StringWrapper("after passing");

     // this will change the object that testParameter is pointing/referring
     // to but it doesn't change testVariable unless you use a reference
     // parameter as indicated in other answers
 }
Dave Cousineau
sumber
-1

Mencoba:


public static void TestI(ref string test)
    {
        test = "after passing";
    }
Marius Kjeldahl
sumber
3
Jawaban Anda harus mengandung lebih dari sekadar kode. Itu juga harus berisi penjelasan mengapa itu bekerja.
Charles Caldwell