1. Giriş
C#-da default olaraq bütün metod parametrləri dəyər üzrə ötürülür, yəni metoda orijinal yox, onun kopiyası düşür.
Bəs əgər məqsəd – xarici dəyişəni metodun içindən dəyişməkdirsə? Yaxud metoddandan birdən bir neçə dəyər qaytarmaq lazımdırsa? Yaxud, bəlkə, metoda böyük bir strukturu ötürmək lazımdır, amma bütün bu "sapa-sapa çamadanı" kopyalamaq istəmirsən, sadəcə ona referans ötürmək kifayətdir ki, yaddaşa qənaət olunsun?
Bütün bunlar – parametr modifikatorları haqqındadır. Onlar sənə çağıran kodla metod arasında məlumatın "ötürülmə marşrutuna" nəzarət imkanı verir.
Modifikatorların növləri
| Modifikator | Məlumat ötürülməsi | Çağırışdan əvvəl inisializasiya lazımdır? | Daxildə oxumaq olar? | Daxildə yazmaq olar? | Tipik istifadə ssenariləri |
|---|---|---|---|---|---|
| ref | Hər iki istiqamətə | Bəli | Bəli | Bəli | Dəyişəni oxumaq və/və ya dəyişmək üçün ötürmək |
| out | Yalnız çölə | Xeyr | Xeyr (təyin olunana qədər) | Bəli (mütləq!) | Metoddan bir neçə dəyəri qaytarmaq |
| in | Yalnız içəri | Bəli | Bəli | Xeyr | Böyük strukturları "yalnız oxumaq" üçün ötürmək və yaddaşa qənaət |
Narahat olma, indi hər birinə ayrıca baxacağıq. Bu, göründüyü qədər çətin deyil.
2. ref modifikatoru: Referansla ötürürük: həm oxuyuruq, həm yazırıq
Təsəvvür elə: dostuna bir fincan qəhvə gətirirsən və deyirsən – "Buyur, istəsən iç, istəsən qızdır, hətta ora nəsə əlavə elə". Dostun bu qəhvə ilə istədiyini edə bilər, nə qalacaqsa (ya da qalmayacaqsa) – sənə qayıdacaq. ref də məhz belə işləyir.
void DoSomething(ref int x)
{
x = x + 10; // Giriş parametrini dəyişirik
}
Parametri ref kimi ötürmək üçün onu həm metodun tərifində, həm də çağırışda bu modifikatorla yazmaq lazımdır:
int myNumber = 5;
DoSomething(ref myNumber);
Console.WriteLine(myNumber); // 15 çap edəcək
Mütləq dəyişən olmalıdır, ifadəni referansla ötürmək olmaz. Belə kod işləməyəcək:
DoSomething(ref 10); // burada dəyişən olmalıdır!
- Çağırışdan əvvəl: dəyişən inisializasiya olunmalıdır (dəyər verilməlidir).
- Metodun içində: həm oxumaq, həm də dəyişmək olar.
- Çağırışdan sonra: dəyişikliklər saxlanılır.
Nümunə: Dəyişənlərin dəyərlərini yerlə dəyişirik
Bəzən iki dəyişənin dəyərlərini yerlə dəyişmək lazımdır. Amma bir nüans var: C#-da dəyər tipli dəyişənlər (məsələn, int) default olaraq dəyər üzrə ötürülür. Bu o deməkdir ki, metoda ötürüləndə onların dəyərləri kopyalanır, dəyişənlərin özləri yox. Metodun çağıran kodun dəyişənlərini dəyişə bilməsi üçün ref açar sözündən istifadə etmək lazımdır.
ref olmadan nümunə — işləmir:
// Dəyərləri dəyişmək cəhdi — alınmayacaq
void Swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
Swap(x, y); // Dəyərlərin kopiyaları ötürülür
Console.WriteLine($"{x}, {y}"); // → 10, 20 — heç nə dəyişmədi!
Swap funksiyasının içində a və b dəyişənləri x və y-nin kopiyalarıdır. Biz yalnız bu kopiyaları dəyişirik, orijinallar toxunulmaz qalır.
ref ilə işləyən nümunə — dəyişənlər yerlə dəyişir
// Dəyərləri ref ilə dəyişirik
void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
Swap(ref x, ref y); // Dəyişənlərin referansları ötürülür
Console.WriteLine($"{x}, {y}"); // → 20, 10 — hər şey düz dəyişdi
ref dəyişənlərin özlərinə referans ötürməyə imkan verir, dəyərlərinə yox.
Ona görə də Swap funksiyası birbaşa x və y ilə işləyir, onların məzmununu dəyişir.
ref nə vaxt istifadə olunur?
- Ötürülən dəyişəni dəyişmək lazımdırsa (məsələn, artırmaq, əvəzləmək və s.).
- Yeni dəyəri return ilə qaytarmağa ehtiyac yoxdursa, çünki arqumenti birbaşa dəyişmək istəyirsən.
- Bir dəstə fərqli dəyəri "boşaltmaq" üçün istifadə etmə — bunun üçün başqa modifikator var.
3. out modifikatoru: Çölə ötürürük
İndi təsəvvür elə: dostunun yanına gəlirsən, amma fincanın boşdur və deyirsən: "Fincanı doldur — nə istəsən, istər qəhvə, istər çay!" Bundan sonra dostun mütləq ora nəsə doldurmalıdır, yoxsa kompilyator dava salacaq.
out ona görə yaradılıb ki, metod bir neçə fərqli dəyəri return olmadan qaytara bilsin. Belə parametrlər metodun içində mütləq inisializasiya olunmalıdır.
void GetCoordinates(out int x, out int y)
{
x = 5;
y = 10; // Bunsuz - səhv!
}
- Çağırışdan əvvəl: dəyişəni inisializasiya etməyə ehtiyac yoxdur, hətta birbaşa out int x yazmaq olar.
- Metodun içində: çıxışdan əvvəl mütləq dəyər vermək lazımdır, yoxsa kompilyator etiraz edəcək.
- Çağırışdan sonra: dəyişəndə yeni dəyər olur.
Nümunə: Metoddan iki dəyəri birbaşa qaytarırıq
void ParseNameAndAge(string input, out string name, out int age)
{
string[] parts = input.Split(','); // sətri massivə çeviririk - ',' ilə bölürük
name = parts[0];
age = int.Parse(parts[1]);
}
string userInput = "İvan,25";
ParseNameAndAge(userInput, out string userName, out int userAge);
Console.WriteLine($"{userName} — {userAge} yaş");
out nə vaxt istifadə olunur?
- Metoddan bir neçə dəyəri qaytarmaq lazımdırsa (məsələn, sətri hissələrə bölmək kimi).
- Qaytarılan dəyərin tipi əvvəlcədən məlum deyilsə (məsələn, sətrin ədədi dəyərə çevrilməsi cəhdi).
.NET-dən nümunələr: int.TryParse(string, out int) — klassikadır!
string input = "123";
if (int.TryParse(input, out int parsedNumber))
{
Console.WriteLine($"Çevrildi: {parsedNumber}");
}
else
{
Console.WriteLine("Çevirmə xətası!");
}
int.TryParse metodu true qaytarır, əgər ötürülən sətri ədədə çevirmək mümkün olubsa və false əks halda. Ədədi dəyəri isə out ilə qaytarır.
ref və out istifadə edərkən tipik səhvlər və gülməli situasiyalar
- ref üçün dəyişəni inisializasiya etməyi unutdun — kompilyator əsəbləşir.
- out üçün dəyər verməyi unutdun — kompilyator daha da əsəbləşir.
- Çağırışda modifikatoru açıq yazmağı unutdun: DoSomething(x) əvəzinə DoSomething(ref x) yazmaq lazımdır.
- ref və ya out ilə konstant və ya literal istifadə etmək olmaz! Yalnız dəyişənlər. Yalnız onların yaddaşda ünvanı var və ona referans vermək olar.
4. in modifikatoru: Yalnız oxumaq üçün, yalnız referansla
Bəzən elə olur ki, metoda böyük və mürəkkəb obyekt ötürmək lazımdır. Adətən belə obyekt referansla ötürülür, amma bu zaman onu təsadüfən dəyişmək olar — çünki tam giriş imkanı verdik.
Teorik olaraq obyektin tam kopiyasını çıxarıb ötürmək olardı. Onda orijinal dəyişməz qalardı. Amma obyekt çox böyükdürsə, boş-boşuna böyük həcmdə məlumat kopyalayacağıq. Daha sərfəlisi obyektin özünü metoda ötürmək və kompilyatora tapşırmaqdır ki, heç kim onu dəyişməsin.
in — yeni modifikatordur, strukturları referansla, amma yalnız oxumaq üçün ötürməyə imkan verir. Bu, "şüşə altında sərgilənən qiymətli qılınc" kimidir: baxmaq olar, amma əlinə almaq — yox.
void PrintPoint(in Point pt)
{
pt.X = 5; // Səhv! Yalnız oxumaq olar.
Console.WriteLine($"Nöqtə: {pt.X}, {pt.Y}"); //Belə olar
}
- Çağırışdan əvvəl: dəyişən inisializasiya olunmalıdır.
- Metodun içində: yalnız oxumaq olar, dəyişmək olmaz.
- Yaddaşa qənaət: struktur kopyalanmır, referansla ötürülür.
Məsələn, böyük strukturların massivləri üçün istifadə olunur ki, artıq kopyalamalardan qaçmaq olsun. Amma class-larla (referans tipləri) in xüsusi effekt vermir.
Nümunə: Böyük strukturu referansla ötürürük ki, kopyalanmasın
struct BigData
{
public int A, B, C, D, E;
}
void PrintBigData(in BigData data)
{
data.A = 10; // olmaz - yalnız oxumaq üçün!
Console.WriteLine(data.A + data.B + data.C + data.D + data.E);
}
BigData myData = new BigData { A = 10, B = 20, C = 30, D = 40, E = 50 };
PrintBigData(in myData);
5. Tez-tez verilən suallar:
ref/out/in massivlər və referans tipləri ilə istifadə etmək olar?
Bəli, olar! Amma unutma: massivlər özləri artıq referans tipidir. Massivi ref ilə ötürəndə, onun öz referansını dəyişmək olar. Ən çox struktur üçün istifadə olunur.
ref və out arasında fərq nədir, axı hər ikisində dəyər ala bilərəm?
ref üçün dəyişən əvvəlcədən inisializasiya olunmalıdır. out üçün vacib deyil, amma metodun içində MÜTLƏQ ən azı bir dəfə dəyər verilməlidir.
ref/out/in ilə literal (məsələn, 5) istifadə etsəm nə olar?
Kompilyator dərhal səhv verəcək. Yalnız dəyişənlər!
ref/out/in neçə parametr edə bilərəm?
İstədiyin qədər, heç bir limit yoxdur! Amma kodun oxunaqlılığı üçün ehtiyatlı ol: əgər ikidən-üçdən çoxdursa, tuple, struktur və ya class qaytarmaq barədə düşün.
GO TO FULL VERSION