Mengapa menggunakan kata kunci 'ref' saat melewati objek?

290

Jika saya meneruskan objek ke suatu metode, mengapa saya harus menggunakan kata kunci ref? Bukankah ini perilaku default?

Sebagai contoh:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

Outputnya adalah "Bar" yang berarti bahwa objek dilewatkan sebagai referensi.

Ryan
sumber

Jawaban:

298

Pass a refjika Anda ingin mengubah objek itu:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

Setelah memanggil DoSomething, ttidak merujuk ke yang asli new TestRef, tetapi merujuk ke objek yang sama sekali berbeda.

Ini mungkin berguna juga jika Anda ingin mengubah nilai objek yang tidak dapat diubah, misalnya a string. Anda tidak dapat mengubah nilai stringsetelah itu dibuat. Tetapi dengan menggunakan ref, Anda bisa membuat fungsi yang mengubah string untuk yang lain yang memiliki nilai berbeda.

Sunting: Seperti orang lain sebutkan. Ini bukan ide yang baik untuk digunakan refkecuali jika diperlukan. Menggunakan refmemberikan kebebasan metode untuk mengubah argumen untuk sesuatu yang lain, penelepon metode perlu diberi kode untuk memastikan mereka menangani kemungkinan ini.

Juga, ketika tipe parameter adalah objek, maka variabel objek selalu bertindak sebagai referensi ke objek. Ini berarti bahwa ketika refkata kunci digunakan, Anda punya referensi ke referensi. Ini memungkinkan Anda untuk melakukan hal-hal seperti yang dijelaskan dalam contoh yang diberikan di atas. Tetapi, ketika tipe parameter adalah nilai primitif (misalnya int), maka jika parameter ini ditugaskan dalam metode, nilai argumen yang diteruskan akan diubah setelah metode kembali:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}
Scott Langham
sumber
88

Anda perlu membedakan antara "meneruskan referensi dengan nilai", dan "meneruskan parameter / argumen dengan referensi".

Saya telah menulis artikel yang cukup panjang untuk menghindari keharusan menulis dengan hati-hati setiap kali muncul di newsgroup :)

Jon Skeet
sumber
1
Yah saya mengalami masalah saat memutakhirkan VB6 menjadi .Net C # code. Ada tanda tangan fungsi / metode yang mengambil parameter ref, out dan plain. Jadi bagaimana kita bisa lebih baik membedakan perbedaan antara param sederhana vs ref?
bonCodigo
2
@bonCodigo: Tidak yakin apa yang Anda maksud dengan "membedakan lebih baik" - itu adalah bagian dari tanda tangan, dan Anda harus menentukan refdi situs panggilan juga ... di mana lagi Anda ingin dibedakan? Semantiknya juga cukup jelas, tetapi perlu diekspresikan dengan hati-hati (alih-alih "objek dilewatkan dengan referensi" yang merupakan penyederhanaan berlebihan yang umum).
Jon Skeet
saya tidak tahu mengapa visual studio masih tidak menunjukkan secara eksplisit apa yang dilewati
MonsterMMORPG
3
@MonsterMMORPG: Saya tidak tahu apa yang Anda maksud dengan itu, saya khawatir.
Jon Skeet
56

Di .NET saat Anda meneruskan parameter apa pun ke suatu metode, salinan dibuat. Dalam tipe nilai berarti bahwa modifikasi apa pun yang Anda lakukan pada nilai ada di ruang lingkup metode, dan hilang ketika Anda keluar dari metode.

Saat melewati Jenis Referensi, salinan juga dibuat, tetapi itu adalah salinan referensi, yaitu sekarang Anda memiliki DUA referensi dalam memori ke objek yang sama. Jadi, jika Anda menggunakan referensi untuk memodifikasi objek, itu akan dimodifikasi. Tetapi jika Anda memodifikasi referensi itu sendiri - kita harus ingat itu adalah salinan - maka setiap perubahan juga hilang saat keluar dari metode.

Seperti yang dikatakan orang sebelumnya, tugas adalah modifikasi dari referensi, sehingga hilang:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Metode di atas tidak mengubah objek asli.

Sedikit modifikasi contoh Anda

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }
Ricardo Amores
sumber
6
Saya suka jawaban ini lebih baik daripada jawaban yang diterima. Ini menjelaskan lebih jelas apa yang terjadi ketika melewati variabel tipe referensi dengan kata kunci ref. Terima kasih!
Stefan
17

Karena TestRef adalah kelas (yang merupakan objek referensi), Anda dapat mengubah konten di dalam t tanpa meneruskannya sebagai referensi. Namun, jika Anda lulus t sebagai ref, TestRef dapat mengubah apa yang t merujuk asli. yaitu membuatnya menunjuk ke objek yang berbeda.

Ferruccio
sumber
16

Dengan refAnda dapat menulis:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

Dan t akan diubah setelah metode selesai.

Rinat Abdullin
sumber
8

Pikirkan variabel (misalnya foo) dari jenis referensi (misalnya List<T>) sebagai pengenal objek memegang bentuk "Objek # 24601". Misalkan pernyataan foo = new List<int> {1,5,7,9};menyebabkan foomenahan "Objek # 24601" (daftar dengan empat item). Kemudian panggilan foo.Lengthakan menanyakan Object # 24601 untuk panjangnya, dan itu akan menjawab 4, sehingga foo.Lengthakan sama dengan 4.

Jika fooditeruskan ke metode tanpa menggunakan ref, metode itu mungkin membuat perubahan ke Objek # 24601. Sebagai konsekuensi dari perubahan tersebut, foo.Lengthmungkin tidak lagi sama 4. Metode itu sendiri, bagaimanapun, tidak akan dapat berubah foo, yang akan terus memegang "Objek # 24601".

Melewati foosebagai refparameter akan memungkinkan metode yang dipanggil untuk melakukan perubahan tidak hanya untuk Objek # 24601, tetapi juga untuk foodirinya sendiri. Metode ini dapat membuat Object # 8675309 baru dan menyimpan referensi itu di foo. Jika demikian, footidak akan lagi memegang "Object # 24601", melainkan "Object # 8675309".

Dalam praktiknya, variabel tipe referensi tidak memiliki string dari bentuk "Object # 8675309"; mereka bahkan tidak memiliki apa pun yang secara bermakna dapat diubah menjadi angka. Meskipun setiap variabel tipe referensi akan menampung beberapa pola bit, tidak ada hubungan tetap antara pola bit yang disimpan dalam variabel tersebut dan objek yang mereka identifikasi. Tidak ada cara kode dapat mengekstraksi informasi dari suatu objek atau referensi ke sana, dan kemudian menentukan apakah referensi lain mengidentifikasi objek yang sama, kecuali jika kode tersebut memiliki atau mengetahui referensi yang mengidentifikasi objek asli.

supercat
sumber
5

Ini seperti melewatkan pointer ke pointer di C. Dalam. NET ini akan memungkinkan Anda untuk mengubah apa yang T asli merujuk, secara pribadi meskipun saya pikir jika Anda melakukan itu di. NET Anda mungkin punya masalah desain!

pezi_pink_squirrel
sumber
3

Dengan menggunakan refkata kunci dengan tipe referensi, Anda secara efektif menyampaikan referensi ke referensi. Dalam banyak hal itu sama dengan menggunakan outkata kunci tetapi dengan perbedaan kecil bahwa tidak ada jaminan bahwa metode tersebut benar-benar akan menetapkan apa pun ke refparameter 'ed.

Isak Savo
sumber
3

ref meniru (atau berperilaku) sebagai area global hanya untuk dua lingkup:

  • Penelepon
  • Callee.
guneysus
sumber
1

Namun, jika Anda memberikan nilai, segalanya berbeda. Anda dapat memaksa nilai untuk dilewatkan dengan referensi. Ini memungkinkan Anda untuk mengirim bilangan bulat ke metode, misalnya, dan meminta metode memodifikasi bilangan bulat atas nama Anda.

Andrew
sumber
4
Apakah Anda melewati referensi atau nilai tipe nilai, perilaku default adalah melewati nilai. Anda hanya perlu memahami bahwa dengan tipe referensi, nilai yang Anda sampaikan adalah referensi. Hal ini tidak sama dengan melewati oleh referensi.
Jon Skeet
1

Ref menunjukkan apakah fungsi bisa mendapatkan objek itu sendiri, atau hanya pada nilainya.

Melewati dengan referensi tidak terikat pada suatu bahasa; itu adalah strategi pengikatan parameter di sebelah nilai demi nilai, pass by name, pass by need, dll ...

A sidenote: nama kelas TestRefadalah pilihan yang sangat buruk dalam konteks ini;).

xtofl
sumber