Varsayılan olarak C# her argümanı değerle geçirir: metot bir kopya alır ve parametreye atama yapmak çağıranı etkilemez. ref, out ve in belirleyicileri bunun yerine çağıranın değişkeninin kendisini geçirir.
Çıktı:
by value: 50
by ref: 60
Anahtar kelime iki kez görünür: parametre listesinde (ref int score) ve çağrı yerinde (ref score). Çağrıda yazmak zorunludur ve bilinçlidir: AddPoints(ref score)'u okuyan herkes score'un değişebileceğini bilir. Unutursanız derleyici CS1620, Argument 1 must be passed with the 'ref' keyword bildirir.
ref: Çağıranın Değişkenini Okumak ve Yazmak
Bir ref parametresi çağıranın değişkeni için bir takma addır. Metot geçerli değeri okuyabilir ve değiştirebilir. Klasik örnek, düz parametrelerle imkânsız olan bir takastır:
Çıktı:
Linus Ada
volume 100
ref için kurallar:
- Argüman bir değişken olmalıdır (yerel bir değişken, bir parametre, bir alan ya da bir dizi elemanı), bir literal ya da bir ifade değil. Bir property de uygun değildir:
Clamp(ref player.Volume, 0, 100)CS0206 hatasıdır,A non ref-returning property or indexer may not be used as an out or ref value. Property'yi yerel bir değişkene kopyalayın, onu geçirin ve geri atayın. - Değişken çağrıdan önce kesin olarak atanmış olmalıdır.
int n; Clamp(ref n, 0, 10);CS0165'tir,Use of unassigned local variable 'n'.
out: Ek Sonuçlar
out metodun ürettiği değerler içindir. Çağıranın değişkeni başlatması gerekmez ve metot dönmeden önce her yolda onu atamak zorundadır:
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];
}
Metodun içinde bir out parametresi siz ona yazana kadar atanmamış sayılır, bu yüzden çağıranın eski değerini okuyamazsınız. ref'ten farkı budur: ref iki yönlüdür, out metottan çağırana tek yönlüdür.
TryParse Kalıbı
out'un en bilinen kullanımı temel kütüphanedeki Try kalıbıdır: başarı için bool döndürün ve sonucu bir out parametresine koyun; böylece hatalı girdi asla istisna fırlatmaz:
Çıktı:
"vip25" is a coupon: 25% off
"42" is a quantity: 42
"BOGUS" is not recognized
"" is not recognized
out var quantity (C# 7.0) değişkeni doğrudan çağrıda bildirir; tipi parametreden gelir. Başarısızlıkta TryParse onu 0 yapar ve false döndürür. Yalnızca parse işleminin başarılı olup olmadığıyla ilgileniyorsanız değeri out _ ile atın: bool isNumber = int.TryParse(input, out _);.
TryGetDiscount'un rate'i iki yolda da nasıl atadığına dikkat edin: bir kez açıkça ve bir kez de kendisi bir out çağrısı olan Dictionary.TryGetValue'ya ileterek.
out ile Tuple Döndürmek
C# 7'den önce birkaç değer döndürmenin ana yolu out'tu. Artık tuple'lar bunu daha doğrudan yapar:
// 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);
Düz çoklu sonuçlar için bir tuple (ya da küçük bir sınıf) kullanın. out'u, bool dönüşün çağrının bir if koşulunda durmasını sağladığı Try kalıbı için saklayın.
Referans Tipleri: Nesneyi Değiştirmek ile Yerine Başkasını Koymak
Bir sınıf örneğini değerle geçirmek, metodun nesneyi değiştirmesine zaten izin verir, çünkü kopyalanan değer aynı nesneye bir referanstır. ref bir yetenek daha ekler: çağıranın değişkeninin gösterdiği nesnenin yerine başkasını koymak.
Çıktı:
after AddItem: 2 items
after ResetByValue: 2 items
after ResetByRef: 0 items
ResetByValue referansın kendi kopyasını yeni bir listeye yönlendirdi; çağıran bunu hiç görmedi. ResetByRef ise çağıranın değişkeninin kendisini değiştirdi. Günlük kodda bir sınıf parametresinde nadiren ref gerekir: bunun yerine yeni nesneyi döndürün.
Struct'lar için fark daha büyüktür, çünkü bir struct argümanı alan alan kopyalanır. Çağıranın struct'ını (bir Point, büyük bir ayarlar struct'ı) değiştirmesi gereken bir metot ref gerektirir.
in: Referansla Salt Okunur
C# 7.2, referansla geçiren ama atamayı yasaklayan in'i ekledi. Amacı performanstır: büyük bir struct her çağrıda kopyalanmaz ve çağırana değişmeyeceği garanti edilir.
// 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
}
Çağrı yerinde in isteğe bağlıdır: Trace(matrix) ve Trace(in matrix) ikisi de çalışır. int, double, DateTime ve diğer küçük tipler için in hiçbir şey kazandırmaz ve biraz daha yavaş olabilir; onu sık geçirdiğiniz, birkaç alanlı struct'lar için kullanın.
Kısıtlamalar
refveoutparametreleri isteğe bağlı olamaz (CS1741). Birinparametresi olabilir:static void Connect(in int retries = 3)geçerlidir.- Overload'lar yalnızca
refileout(ya dain) arasında farklılaşamaz, çünkü çalışma zamanı seviyesinde aynıdırlar.F(int x)veF(ref int x)bir arada var olabilir. asyncmetotlar ve iterator'lar (yield returnkullanan metotlar)ref,outya dainparametrelerine sahip olamaz. Onlardan bunun yerine bir tuple ya da bir sonuç nesnesi döndürün.- Bir lambda ya da yerel fonksiyon çevreleyen metodun bir
ref,outya dainparametresini yakalayamaz; önce onu yerel bir değişkene kopyalayın.
ref, out ve in Karşılaştırması
| Çağıran önce atamalı | Metot atamalı | Metot okuyabilir | Çağrı yeri anahtar kelimesi | |
|---|---|---|---|---|
| (yok) | evet | hayır | evet (bir kopya) | yok |
ref | evet | hayır | evet | zorunlu |
out | hayır | evet, her yolda | yalnızca atadıktan sonra | zorunlu |
in | evet | izin verilmez | evet | isteğe bağlı |
Sıkça Sorulan Sorular
C#'ta ref ile out arasındaki fark nedir?
İkisi de kopya yerine çağıranın değişkeninin kendisini geçirir. ref ile değişken çağrıdan önce atanmış olmalıdır ve metot onu okuyup değiştirebilir. out ile değişkenin önce atanması gerekmez, metot onu yazmadan önce okuyamaz ve dönmeden önce atamak zorundadır (aksi halde CS0177). out "bu ek bir sonuçtur" demektir.
C#'ta out var ne demek?
C# 7.0'dan beri out değişkenini çağrının içinde bildirebilirsiniz: if (int.TryParse(text, out var number)). Değişkenin tipi parametreden çıkarılır ve ifadeden sonra da kapsamdadır. out _ ihtiyacınız olmayan bir sonucu atar.
C#'ta nesneler referansla mı geçirilir?
Hayır, ref, out ya da in yazmadıkça her argüman değerle geçirilir. Bir sınıf için kopyalanan değer bir referanstır, bu yüzden metot nesnenin alanlarını değiştirebilir ve çağıran bunu görür. ref olmadan yapamayacağı şey, çağıranın değişkeninin farklı bir nesneyi göstermesini sağlamaktır.
C#'ta in parametre belirleyicisi nedir?
in (C# 7.2 ve sonrası) bir argümanı referansla ama salt okunur geçirir: metot ona atama yapamaz. Her çağrıda büyük struct'ları kopyalamaktan kaçınmak için vardır. int ya da DateTime gibi küçük tipler için hiçbir fayda getirmez.
Neden "Argument 1 must be passed with the 'ref' keyword" hatası alıyorum?
Metot bir ref parametresi bildiriyor ve çağrı anahtar kelimeyi yazmamış. C#, okuyucular değişkenin değişebileceğini görebilsin diye ref'i (ve out'u) çağrı yerinde de ister: Increment(count); değil Increment(ref count);.