Kapan menggunakan ref dan kapan tidak perlu di C #

104

Saya memiliki objek yang saya dalam status memori program dan juga memiliki beberapa fungsi pekerja lain yang saya berikan objek untuk mengubah status. Saya telah meneruskannya dengan ref ke fungsi pekerja. Namun saya menemukan fungsi berikut.

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

Ini membingungkan saya karena keduanya received_sdan remoteEPmengembalikan barang dari fungsi. Mengapa remoteEPperlu refdan received_stidak?

Saya juga seorang programmer ac jadi saya mengalami masalah dalam mendapatkan petunjuk dari kepala saya.

Sunting: Sepertinya objek di C # adalah penunjuk ke objek di bawah tenda. Jadi, saat Anda meneruskan objek ke suatu fungsi, Anda kemudian dapat mengubah konten objek melalui penunjuk dan satu-satunya hal yang diteruskan ke fungsi tersebut adalah penunjuk ke objek sehingga objek itu sendiri tidak sedang disalin. Anda menggunakan ref atau keluar jika Anda ingin dapat beralih atau membuat objek baru dalam fungsi yang seperti penunjuk ganda.

Rex Logan
sumber

Jawaban:

217

Jawaban singkat: baca artikel saya tentang penyampaian argumen .

Jawaban panjang: ketika parameter tipe referensi dilewatkan oleh nilai, hanya referensi yang diteruskan, bukan salinan objek. Ini seperti melewatkan pointer (dengan nilai) di C atau C ++. Perubahan pada nilai parameter itu sendiri tidak akan terlihat oleh pemanggil, tetapi perubahan pada objek yang dirujuk akan terlihat.

Ketika parameter (dalam bentuk apa pun) dilewatkan oleh referensi, itu berarti bahwa setiap perubahan pada parameter dilihat oleh pemanggil - perubahan pada parameter adalah perubahan pada variabel.

Artikel tersebut menjelaskan semua ini secara lebih rinci, tentu saja :)

Jawaban yang berguna: Anda hampir tidak perlu menggunakan ref / out . Ini pada dasarnya adalah cara untuk mendapatkan nilai pengembalian yang lain, dan biasanya harus dihindari karena itu berarti metode tersebut mungkin mencoba melakukan terlalu banyak. Itu tidak selalu terjadi ( TryParsedll adalah contoh kanonik dari penggunaan yang wajar out) tetapi menggunakan ref / out harus relatif jarang.

Jon Skeet
sumber
38
Saya pikir Anda memiliki jawaban pendek dan jawaban panjang yang campur aduk; itu artikel besar!
Outlaw Programmer
23
@Outlaw: Ya, tapi jawaban singkatnya sendiri, arahan untuk membaca artikel, panjangnya hanya 6 kata :)
Jon Skeet
27
Referensi! :)
gonzobrains
5
@Liam Menggunakan ref seperti yang Anda lakukan mungkin terlihat lebih eksplisit bagi Anda, tetapi sebenarnya dapat membingungkan pemrogram lain (mereka yang tahu apa kata kunci itu), karena pada dasarnya Anda memberi tahu calon penelepon, "Saya dapat mengubah variabel Anda digunakan dalam metode pemanggilan, yaitu menetapkan ulang ke objek yang berbeda (atau bahkan ke null, jika memungkinkan) jadi jangan bertahan, atau pastikan Anda memvalidasinya ketika saya sudah selesai ". Itu cukup kuat, dan sama sekali berbeda dari "objek ini dapat dimodifikasi", yang selalu terjadi setiap kali Anda meneruskan referensi objek sebagai parameter.
mbargiel
1
@ M.Mimpen: C # 7 (semoga) mengizinkanif (int.TryParse(text, out int value)) { ... use value here ... }
Jon Skeet
26

Pikirkan parameter non-ref sebagai penunjuk, dan parameter ref sebagai penunjuk ganda. Ini paling membantu saya.

Anda hampir tidak boleh melewatkan nilai dengan ref. Saya menduga bahwa jika bukan karena masalah interop, tim .Net tidak akan pernah memasukkannya ke dalam spesifikasi aslinya. Cara OO untuk menangani sebagian besar masalah yang dipecahkan oleh parameter ref adalah dengan:

Untuk beberapa nilai pengembalian

  • Buat struct yang mewakili beberapa nilai kembali

Untuk primitif yang berubah dalam metode sebagai hasil dari pemanggilan metode (metode memiliki efek samping pada parameter primitif)

  • Menerapkan metode dalam objek sebagai metode instance dan memanipulasi status objek (bukan parameter) sebagai bagian dari pemanggilan metode
  • Gunakan solusi beberapa nilai pengembalian dan gabungkan nilai yang dikembalikan ke negara bagian Anda
  • Buat objek yang berisi status yang dapat dimanipulasi dengan metode dan meneruskan objek itu sebagai parameter, dan bukan primitif itu sendiri.
Michael Meadows
sumber
8
Ya Tuhan. Saya harus membaca 20x ini untuk memahaminya. Kedengarannya seperti pekerjaan ekstra bagi saya hanya untuk melakukan sesuatu yang sederhana.
PositiveGuy
Kerangka .NET tidak mengikuti aturan # 1 Anda. ('Untuk beberapa nilai kembali, buat struct'). Ambil contoh IPAddress.TryParse(string, out IPAddress).
Swen Kooij
@SwenKooij Anda benar. Saya berasumsi bahwa di sebagian besar tempat di mana mereka menggunakan parameter itu baik (a) Mereka membungkus Win32 API, atau (b) itu adalah hari-hari awal, dan programmer C ++ membuat keputusan.
Michael Meadows
@SwenKooij Saya tahu balasan ini sudah terlambat bertahun-tahun, tetapi ternyata. Kita sudah terbiasa dengan TryParse, tetapi bukan berarti itu bagus. Akan lebih baik jika daripada if (int.TryParse("123", out var theInt) { /* use theInt */ }kita memilikinya var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ }Ini lebih banyak kode, tetapi jauh lebih konsisten dengan desain bahasa C #.
Michael Meadows
9

Anda mungkin bisa menulis seluruh aplikasi C # dan tidak pernah melewatkan objek / struct oleh ref.

Saya memiliki seorang profesor yang memberi tahu saya ini:

Satu-satunya tempat Anda akan menggunakan referensi adalah tempat Anda:

  1. Ingin mengirimkan objek besar (yaitu, objek / struct memiliki objek / struct di dalamnya ke beberapa tingkatan) dan menyalinnya akan mahal dan
  2. Anda memanggil Framework, Windows API atau API lain yang membutuhkannya.

Jangan lakukan itu hanya karena Anda bisa. Anda bisa mendapatkan sedikit masalah dengan beberapa bug jahat jika Anda mulai mengubah nilai dalam parameter dan tidak memperhatikan.

Saya setuju dengan sarannya, dan dalam lima tahun lebih saya sejak sekolah, saya tidak pernah membutuhkannya selain memanggil Framework atau Windows API.

Chris
sumber
3
Jika Anda berencana untuk mengimplementasikan "Swap", melewati ref bisa membantu.
Brian
@ Chris akan membuat masalah jika saya menggunakan kata kunci ref untuk benda-benda kecil?
ManirajSS
@TechnikEmpire "Tapi, perubahan pada objek dalam lingkup fungsi yang dipanggil tidak direfleksikan kembali ke pemanggil." Itu salah. Jika saya meneruskan Orang ke SetName(person, "John Doe"), properti nama akan berubah dan perubahan itu akan tercermin ke pemanggil.
M. Mimpen
@ M.Mimpen Komentar dihapus. Saya baru saja mengambil C # pada saat itu dan dengan jelas berbicara tentang * $$ saya. Terima kasih telah memberitahukannya kepada saya.
@ Chris - Saya cukup yakin memberikan benda "besar" tidaklah mahal. Jika Anda hanya melewatkannya dengan nilai, Anda masih hanya meneruskan satu penunjuk, bukan?
Ian
3

Karena accept_s adalah larik, Anda meneruskan penunjuk ke larik itu. Fungsi memanipulasi data yang sudah ada di tempatnya, tidak mengubah lokasi atau penunjuk yang mendasarinya. Kata kunci ref menandakan bahwa Anda meneruskan penunjuk yang sebenarnya ke lokasi dan memperbarui penunjuk itu di fungsi luar, sehingga nilai di fungsi luar akan berubah.

Misalnya byte array adalah penunjuk ke memori yang sama sebelum dan sesudah, memori tersebut baru saja diperbarui.

Referensi Endpoint sebenarnya memperbarui penunjuk ke Endpoint di fungsi luar ke instance baru yang dihasilkan di dalam fungsi.

Chris Hynes
sumber
3

Pikirkan ref sebagai arti Anda melewati pointer dengan referensi. Tidak menggunakan ref berarti Anda melewatkan pointer dengan nilai.

Lebih baik lagi, abaikan apa yang baru saja saya katakan (mungkin menyesatkan, terutama dengan tipe nilai) dan baca halaman MSDN ini .

Brian
sumber
Sebenarnya tidak benar. Setidaknya bagian kedua. Jenis referensi apa pun akan selalu diteruskan oleh referensi, apakah Anda menggunakan ref atau tidak.
Erik Funkenbusch
Sebenarnya, jika direnungkan lebih lanjut, itu tidak benar. Referensi sebenarnya diteruskan oleh nilai tanpa tipe ref. Artinya, mengubah nilai yang ditunjukkan oleh referensi mengubah data asli, tetapi mengubah referensi itu sendiri tidak mengubah referensi asli.
Erik Funkenbusch
2
Jenis referensi non-referensi tidak diteruskan oleh referensi. Referensi ke tipe referensi diteruskan oleh nilai. Tetapi jika Anda ingin menganggap referensi sebagai penunjuk ke sesuatu yang direferensikan, apa yang saya katakan masuk akal (tetapi berpikir seperti itu mungkin menyesatkan). Oleh karena itu peringatan saya.
Brian
Tautan sudah mati - coba yang ini - docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
GIVE-ME-CHICKEN
0

pemahaman saya adalah bahwa semua objek yang berasal dari kelas Object diteruskan sebagai pointer sedangkan tipe biasa (int, struct) tidak dilewatkan sebagai pointer dan memerlukan ref. Saya tidak yakin tentang string (apakah itu pada akhirnya berasal dari kelas Object?)

Lucas
sumber
Ini bisa menggunakan beberapa klarifikasi. Apa perbedaan antara nilai- dan referensi tyoes. Ans mengapa itu relevan dengan menggunakan kata kunci ref pada parameter?
oɔɯǝɹ
Dalam .net, semuanya kecuali pointer, parameter tipe, dan antarmuka diturunkan dari Object. Penting untuk dipahami, bahwa tipe "biasa" (dengan tepat disebut "tipe nilai") juga mewarisi dari objek. Anda benar sejak itu: Jenis nilai (per default) diteruskan oleh nilai, sedangkan jenis referensi diteruskan oleh referensi. Jika Anda ingin mengubah tipe nilai, daripada mengembalikan yang baru (menanganinya seperti tipe referensi di dalam metode), Anda harus menggunakan kata kunci ref di atasnya. Tapi itu gaya yang buruk dan Anda tidak boleh melakukannya kecuali Anda benar-benar yakin bahwa Anda harus :)
buddybubble
1
untuk menjawab pertanyaan Anda: string diturunkan dari objek. Ini adalah tipe referensi yang berperilaku seperti tipe nilai (untuk kinerja dan alasan logis)
buddybubble
0

Sementara saya setuju dengan jawaban Jon Skeet secara keseluruhan dan beberapa jawaban lainnya, ada kasus penggunaan untuk digunakan ref, dan itu untuk memperketat pengoptimalan kinerja. Telah diamati selama pembuatan profil kinerja bahwa pengaturan nilai kembalian suatu metode memiliki sedikit implikasi kinerja, sedangkan menggunakan refsebagai argumen di mana nilai kembalian diisi ke dalam parameter itu menghasilkan sedikit kemacetan yang dihilangkan.

Ini benar-benar hanya berguna jika upaya pengoptimalan dilakukan ke tingkat ekstrem, mengorbankan keterbacaan dan mungkin kemampuan pengujian dan pemeliharaan untuk menghemat milidetik atau mungkin sepersekian milidetik.

Jon Davis
sumber
-1

Dasar nol aturan pertama, Primitif diteruskan oleh nilai (tumpukan) dan Non-Primitif dengan referensi (Heap) dalam konteks JENIS yang terlibat.

Parameter yang terlibat diteruskan oleh Nilai secara default. Pos bagus yang menjelaskan banyak hal secara detail. http://yoda.arachsys.com/csharp/parameters.html

Student myStudent = new Student {Name="A",RollNo=1};

ChangeName(myStudent);

static void ChangeName(Student s1)
{
  s1.Name = "Z"; // myStudent.Name will also change from A to Z
                // {AS s1 and myStudent both refers to same Heap(Memory)
                //Student being the non-Primitive type
}

ChangeNameVersion2(ref myStudent);
static void ChangeNameVersion2(ref Student s1)
{
  s1.Name = "Z"; // Not any difference {same as **ChangeName**}
}

static void ChangeNameVersion3(ref Student s1)
{
    s1 = new Student{Name="Champ"};

    // reference(myStudent) will also point toward this new Object having new memory
    // previous mystudent memory will be released as it is not pointed by any object
}

Kita dapat mengatakan (dengan peringatan) Jenis non-primitif hanyalah Pointer Dan ketika kita melewatinya ref kita dapat mengatakan kita melewati Pointer Ganda

Pengungsi Singh Malik
sumber
Semua parameter diteruskan dengan nilai, secara default, di C #. Setiap parameter dapat dikirimkan dengan referensi. Untuk tipe referensi, nilai yang diteruskan (baik dengan referensi atau nilai) itu sendiri adalah referensi. Itu sepenuhnya terlepas dari bagaimana itu disebarkan.
Pelayanan
Setuju @Servy! "Saat kita mendengar kata" referensi "atau" nilai "yang digunakan, kita harus sangat memahami apakah yang kita maksud bahwa parameter adalah referensi atau parameter nilai, atau apakah yang kita maksud adalah jenis yang terlibat adalah referensi atau jenis nilai 'Little Kebingungan di luar sana di pihak saya, Ketika saya mengatakan aturan Ground zero terlebih dahulu, Primitif dilewatkan oleh nilai (tumpukan) dan Non-Primitif dengan referensi (Heap) yang saya maksud dari TYPES yang terlibat, Bukan Parameter! Ketika kita berbicara tentang Parameter, Anda Benar , Semua parameter diteruskan dengan nilai, secara default di C #.
Surender Singh Malik
Mengatakan bahwa parameter adalah referensi atau parameter nilai bukanlah istilah standar, dan benar-benar ambigu tentang apa yang Anda maksud. Jenis parameter dapat berupa jenis referensi atau jenis nilai. Parameter apa pun dapat diteruskan dengan nilai atau referensi. Ini adalah konsep ortogonal untuk parameter tertentu. Posting Anda salah menjelaskan ini.
Pelayanan
1
Anda melewatkan parameter dengan referensi atau nilai. Istilah "menurut referensi" dan "menurut nilai" tidak digunakan untuk menjelaskan apakah suatu tipe adalah tipe nilai atau tipe referensi.
Pelayanan
1
Tidak, ini bukan masalah persepsi. Ketika Anda menggunakan istilah yang salah untuk merujuk pada sesuatu, maka pernyataan itu menjadi salah . Pernyataan yang benar perlu menggunakan terminologi yang benar untuk merujuk pada konsep.
Pelayanan