Secara default, C# meneruskan setiap argumen by value: method mendapat salinan, dan mengisi parameter tidak memengaruhi pemanggil. Modifier ref, out, dan in meneruskan variabel milik pemanggil itu sendiri.
Output:
by value: 50
by ref: 60
Kata kuncinya muncul dua kali: di daftar parameter (ref int score) dan di tempat pemanggilan (ref score). Menuliskannya di pemanggilan itu wajib, dan memang disengaja: siapa pun yang membaca AddPoints(ref score) tahu bahwa score mungkin berubah. Jika kamu melupakannya, compiler melaporkan CS1620, Argument 1 must be passed with the 'ref' keyword.
ref: membaca dan menulis variabel pemanggil
Parameter ref adalah alias untuk variabel milik pemanggil. Method bisa membaca nilai saat ini dan menggantinya. Contoh klasiknya adalah swap, yang tidak mungkin dilakukan dengan parameter biasa:
Output:
Linus Ada
volume 100
Aturan untuk ref:
- Argumennya harus berupa variabel (variabel lokal, parameter, field, atau elemen array), bukan literal atau ekspresi. Property juga tidak memenuhi syarat:
Clamp(ref player.Volume, 0, 100)adalah error CS0206,A non ref-returning property or indexer may not be used as an out or ref value. Salin property ke variabel lokal, teruskan variabel itu, lalu isi kembali property-nya. - Variabelnya harus sudah pasti diberi nilai sebelum pemanggilan.
int n; Clamp(ref n, 0, 10);adalah CS0165,Use of unassigned local variable 'n'.
out: hasil tambahan
out dipakai untuk nilai yang dihasilkan method. Pemanggil tidak perlu menginisialisasi variabelnya, dan method wajib mengisinya di setiap jalur sebelum kembali:
static void Split(string fullName, out string first, out string last)
{
// error CS0177: The out parameter 'last' must be assigned to before control leaves the current method
first = fullName.Split(' ')[0];
}
Di dalam method, parameter out dianggap belum diberi nilai sampai kamu menulisinya, jadi kamu tidak bisa membaca nilai lama milik pemanggil. Itulah perbedaannya dengan ref: ref dua arah, out satu arah dari method ke pemanggil.
Pola TryParse
Pemakaian out yang paling terkenal adalah pola Try di library bawaan: kembalikan bool untuk menandai berhasil, lalu taruh hasilnya di parameter out, sehingga input yang salah tidak pernah melempar exception:
Output:
"vip25" is a coupon: 25% off
"42" is a quantity: 42
"BOGUS" is not recognized
"" is not recognized
out var quantity (C# 7.0) mendeklarasikan variabel langsung di dalam pemanggilan; tipenya diambil dari parameternya. Jika gagal, TryParse mengisinya dengan 0 dan mengembalikan false. Jika kamu hanya peduli apakah parsing berhasil, buang nilainya dengan out _: bool isNumber = int.TryParse(input, out _);.
Perhatikan bagaimana TryGetDiscount mengisi rate di kedua jalur: sekali secara eksplisit, dan sekali dengan meneruskannya ke Dictionary.TryGetValue, yang juga merupakan pemanggilan out.
out vs mengembalikan tuple
Sebelum C# 7, out adalah cara utama untuk mengembalikan beberapa nilai. Sekarang tuple melakukannya dengan lebih langsung:
// with out parameters
static void MinMax(int[] values, out int min, out int max) { ... }
MinMax(temps, out int lo, out int hi);
// with a tuple
static (int Min, int Max) MinMax(int[] values) { ... }
var (lo, hi) = MinMax(temps);
Pakai tuple (atau class kecil) untuk beberapa hasil biasa. Simpan out untuk pola Try, karena nilai kembalian bool membuat pemanggilannya bisa ditaruh di kondisi if.
Reference type: mengubah object vs menggantinya
Meneruskan instance class by value sudah memungkinkan method mengubah object-nya, karena nilai yang disalin adalah referensi ke object yang sama. ref menambahkan satu kemampuan lagi: mengganti object yang ditunjuk oleh variabel milik pemanggil.
Output:
after AddItem: 2 items
after ResetByValue: 2 items
after ResetByRef: 0 items
ResetByValue mengarahkan salinan referensinya sendiri ke list baru; pemanggil tidak pernah melihatnya. ResetByRef mengubah variabel milik pemanggil itu sendiri. Di kode sehari-hari kamu jarang butuh ref pada parameter class: kembalikan saja object barunya.
Untuk struct, perbedaannya lebih besar, karena argumen struct disalin field demi field. Method yang harus mengubah struct milik pemanggil (sebuah Point, struct pengaturan yang besar) butuh ref.
in: read-only by reference
C# 7.2 menambahkan in, yang meneruskan by reference tetapi melarang pengisian nilai. Tujuannya adalah performa: struct besar tidak disalin di setiap pemanggilan, dan pemanggil dijamin nilainya tidak akan berubah.
// C# 7.2 and later
struct Matrix4
{
public double M11, M12, M13, M14, M21, M22, M23, M24,
M31, M32, M33, M34, M41, M42, M43, M44;
}
static double Trace(in Matrix4 m) => m.M11 + m.M22 + m.M33 + m.M44;
static void Reset(in Matrix4 m)
{
m.M11 = 0;
// error CS8332: Cannot assign to a member of variable 'm' or use it as the right hand side of a ref assignment because it is a readonly variable
}
Di tempat pemanggilan, in bersifat opsional: Trace(matrix) dan Trace(in matrix) sama-sama berjalan. Untuk int, double, DateTime, dan tipe kecil lainnya, in tidak memberi keuntungan dan bisa sedikit lebih lambat; pakai untuk struct dengan beberapa field yang sering kamu teruskan.
Batasan
- Parameter
refdanouttidak bisa opsional (CS1741). Parameterinbisa:static void Connect(in int retries = 3)itu sah. - Overload tidak boleh hanya berbeda antara
refdanout(atauin), karena di tingkat runtime keduanya sama.F(int x)danF(ref int x)bisa ada bersamaan. - Method
asyncdan iterator (method yang memakaiyield return) tidak bisa punya parameterref,out, atauin. Kembalikan tuple atau object hasil dari method seperti itu. - Lambda atau local function tidak bisa menangkap parameter
ref,out, atauindari method yang melingkupinya; salin dulu ke variabel lokal.
Perbandingan ref, out, dan in
| Pemanggil harus mengisi dulu | Method wajib mengisi | Method boleh membaca | Kata kunci saat pemanggilan | |
|---|---|---|---|---|
| (tanpa modifier) | ya | tidak | ya (salinan) | tidak ada |
ref | ya | tidak | ya | wajib |
out | tidak | ya, di setiap jalur | hanya setelah mengisinya | wajib |
in | ya | tidak diizinkan | ya | opsional |
Pertanyaan yang sering diajukan
Apa perbedaan ref dan out di C#?
Keduanya meneruskan variabel milik pemanggil itu sendiri, bukan salinannya. Dengan ref, variabel harus sudah diberi nilai sebelum pemanggilan, dan method boleh membaca serta mengubahnya. Dengan out, variabel tidak perlu diberi nilai lebih dulu, method tidak bisa membacanya sebelum menulisinya, dan method wajib mengisinya sebelum kembali (jika tidak, muncul CS0177). out berarti "ini adalah hasil tambahan".
Apa arti out var di C#?
Sejak C# 7.0 kamu bisa mendeklarasikan variabel out langsung di dalam pemanggilan: if (int.TryParse(text, out var number)). Tipe variabelnya disimpulkan dari parameternya, dan variabel itu juga tetap bisa dipakai setelah pernyataan tersebut. out _ membuang hasil yang tidak kamu butuhkan.
Apakah object diteruskan by reference di C#?
Tidak, setiap argumen diteruskan by value kecuali kamu menulis ref, out, atau in. Untuk class, nilai yang disalin adalah sebuah referensi, jadi method bisa mengubah field object itu dan pemanggil melihat perubahannya. Yang tidak bisa dilakukannya tanpa ref adalah membuat variabel milik pemanggil menunjuk ke object lain.
Apa itu modifier parameter in di C#?
in (C# 7.2 dan sesudahnya) meneruskan argumen by reference, tetapi read-only: method tidak bisa mengisinya. Modifier ini ada untuk menghindari penyalinan struct besar di setiap pemanggilan. Untuk tipe kecil seperti int atau DateTime, tidak ada manfaatnya.
Kenapa muncul error "Argument 1 must be passed with the 'ref' keyword"?
Method-nya mendeklarasikan parameter ref, tetapi pemanggilannya tidak menyertakan kata kunci itu. C# mewajibkan ref (dan out) juga ditulis di tempat pemanggilan, agar pembaca bisa melihat bahwa variabelnya mungkin berubah: Increment(ref count);, bukan Increment(count);.