Daftar disahkan oleh ref - bantu saya menjelaskan perilaku ini

109

Lihat program berikut:

class Test
{
    List<int> myList = new List<int>();

    public void TestMethod()
    {
        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList(myList);

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList(List<int> myList)
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

Saya berasumsi myListakan berlalu ref, dan hasilnya akan

3
4

Daftar ini memang "disahkan oleh ref", tetapi hanya sortfungsi yang berfungsi. Pernyataan berikut myList = myList2;tidak berpengaruh.

Jadi outputnya sebenarnya:

10
50
100

Bisakah Anda membantu saya menjelaskan perilaku ini? Jika memang myListtidak lewat-oleh-ref (seperti yang tampak dari myList = myList2tidak berlaku), bagaimana myList.Sort()efeknya?

Saya bahkan mengasumsikan pernyataan itu tidak berlaku dan hasilnya adalah:

100
50
10
nmdr
sumber
Hanya pengamatan (dan saya menyadari masalahnya telah disederhanakan di sini), tetapi tampaknya lebih baik ChangeListmengembalikan a List<int>daripada menjadi voidjika sebenarnya membuat daftar baru.
Jeff B

Jawaban:

110

Anda melewati sebuah referensi ke dalam daftar , tetapi Anda tidak melewati variabel daftar dengan referensi - sehingga ketika Anda menelepon ChangeListdengan nilai variabel (yaitu referensi - berpikir "pointer") disalin - dan perubahan pada nilai parameter di dalamnya ChangeList tidak dilihat oleh TestMethod.

mencoba:

private void ChangeList(ref List<int> myList) {...}
...
ChangeList(ref myList);

Ini kemudian meneruskan referensi ke variabel lokal myRef (seperti yang dideklarasikan di TestMethod); sekarang, jika Anda menetapkan kembali parameter di dalam ChangeListAnda juga menetapkan kembali variabel di dalamnya TestMethod .

Marc Gravell
sumber
Memang saya bisa melakukannya, tapi saya ingin tahu bagaimana pengaruhnya
nmdr
6
@Ngm - saat Anda memanggil ChangeList, hanya referensi yang disalin - itu adalah objek yang sama. Jika Anda mengubah objek dengan cara tertentu, semua yang memiliki referensi ke objek tersebut akan melihat perubahannya.
Marc Gravell
225

Awalnya, ini dapat direpresentasikan secara grafis sebagai berikut:

Status Init

Kemudian, pengurutan diterapkan myList.Sort(); Sortir koleksi

Akhirnya, ketika Anda melakukannya myList' = myList2:, Anda kehilangan salah satu referensi tetapi bukan yang asli dan koleksi tetap diurutkan.

Referensi hilang

Jika Anda menggunakan by reference ( ref) maka myList'dan myListakan menjadi sama (hanya satu referensi).

Catatan: Saya gunakan myList'untuk mewakili parameter yang Anda gunakan di ChangeList(karena Anda memberi nama yang sama dengan aslinya)

Jaider
sumber
20

Berikut cara mudah untuk memahaminya

  • Daftar Anda adalah objek yang dibuat di heap. Variabel myListadalah referensi ke objek itu.

  • Dalam C # Anda tidak pernah melewatkan objek, Anda meneruskan referensi mereka dengan nilai.

  • Saat Anda mengakses objek daftar melalui referensi yang diteruskan di ChangeList(saat menyortir, misalnya) daftar asli diubah.

  • Penetapan pada ChangeListmetode dibuat ke nilai referensi, oleh karena itu tidak ada perubahan yang dilakukan pada daftar asli (masih di heap tetapi tidak direferensikan pada variabel metode lagi).

Unmesh Kondolikar
sumber
10

Link ini akan membantu Anda dalam memahami referensi lewat di C #. Pada dasarnya, ketika sebuah objek berjenis referensi diteruskan dengan nilai ke suatu metode, hanya metode yang tersedia pada objek itu yang dapat mengubah konten objek.

Misalnya metode List.sort () mengubah konten Daftar tetapi jika Anda menetapkan beberapa objek lain ke variabel yang sama, tugas itu bersifat lokal untuk metode itu. Itulah mengapa myList tetap tidak berubah.

Jika kita melewatkan objek tipe referensi dengan menggunakan kata kunci ref maka kita dapat menetapkan beberapa objek lain ke variabel yang sama dan itu mengubah seluruh objek itu sendiri.

(Sunting: ini adalah versi terbaru dari dokumentasi yang ditautkan di atas.)

Shekhar
sumber
5

C # hanya melakukan salinan dangkal ketika melewati nilai kecuali objek yang dimaksud dieksekusi ICloneable(yang tampaknya tidak oleh Listkelas).

Artinya, ia menyalin Listdirinya sendiri, tetapi referensi ke objek di dalam daftar tetap sama; artinya, penunjuk terus merujuk objek yang sama seperti aslinya List.

Jika Anda mengubah nilai dari hal-hal yang menjadi Listreferensi baru Anda, Anda juga mengubah yang asli List(karena merujuk pada objek yang sama). Namun, Anda kemudian mengubah myListreferensi apa seluruhnya, menjadi yang baru List, dan sekarang hanya yang asli Listyang mereferensikan bilangan bulat tersebut.

Baca bagian Parameter Jenis Referensi Melewati dari artikel MSDN ini di "Parameter yang Melewati" untuk informasi lebih lanjut.

"Bagaimana cara Mengkloning Daftar Generik di C #" dari StackOverflow berbicara tentang cara membuat salinan dalam Daftar.

Ethel Evans
sumber
3

Sementara saya setuju dengan apa yang dikatakan semua orang di atas. Saya memiliki pandangan berbeda tentang kode ini. Pada dasarnya Anda menetapkan daftar baru ke variabel lokal myList bukan global. jika Anda mengubah tanda tangan ChangeList (List myList) menjadi private void ChangeList (), Anda akan melihat output 3, 4.

Inilah alasan saya ... Meskipun list dilewatkan oleh referensi, anggap saja sebagai melewatkan variabel pointer dengan nilai Ketika Anda memanggil ChangeList (myList) Anda meneruskan pointer ke (Global) myList. Sekarang ini disimpan di variabel myList (lokal). Jadi sekarang myList (lokal) dan (global) myList Anda mengarah ke daftar yang sama. Sekarang Anda melakukan sort => ini berfungsi karena (lokal) myList mereferensikan myList (global) asli. Selanjutnya Anda membuat daftar baru dan menetapkan pointer ke myList (lokal) Anda. Tapi begitu fungsi keluar, variabel (lokal) myList dihancurkan. HTH

class Test
{
    List<int> myList = new List<int>();
    public void TestMethod()
    {

        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList();

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList()
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}
sandeep
sumber
2

Gunakan refkata kunci.

Lihat referensi definitif di sini untuk memahami parameter yang lewat.
Untuk lebih spesifik, lihat ini , untuk memahami perilaku kode.

EDIT: Sortbekerja pada referensi yang sama (yang dilewatkan oleh nilai) dan karenanya nilainya diurutkan. Namun, menetapkan contoh baru ke parameter tidak akan berfungsi karena parameter diteruskan oleh nilai, kecuali Anda memasukkan ref.

Puting refmemungkinkan Anda mengubah penunjuk ke referensi ke contoh baru Listdalam kasus Anda. Tanpanya ref, Anda dapat mengerjakan parameter yang ada, tetapi tidak dapat membuatnya menunjuk ke sesuatu yang lain.

shahkalpesh.dll
sumber
0

Ada dua bagian memori yang dialokasikan untuk objek tipe referensi. Satu di tumpukan dan satu lagi di tumpukan. Bagian dalam tumpukan (alias pointer) berisi referensi ke bagian dalam heap - tempat menyimpan nilai aktual.

Jika kata kunci ref tidak digunakan, hanya salinan bagian dalam tumpukan yang dibuat dan diteruskan ke metode - referensi ke bagian yang sama di heap. Oleh karena itu, jika Anda mengubah sesuatu di bagian heap, perubahan itu akan tetap ada. Jika Anda mengubah penunjuk yang disalin - dengan menetapkannya untuk merujuk ke tempat lain di heap - ini tidak akan memengaruhi penunjuk asal di luar metode.

Thinh Tran
sumber