1. Giriş
Həyatda bir çox əməliyyatlar — sanki çoxfunksiyalı isveçrə bıçaqları kimidir: eyni komanda müxtəlif alətlərlə işləyə bilər. Məsələn, təsəvvür elə bankomatı: kartı daxil edirsən — bankomat pin-kod soruşur; telefon nömrəsi daxil edirsən — bankomat SMS-dən təsdiq kodu gözləyir. Əməliyyat bir dənədir — "istifadəçini yoxla", amma üsullar fərqlidir.
Proqramlaşdırmada da tez-tez belə situasiyalar olur: elə bil bir əməliyyat yerinə yetirilməlidir, amma verilənlər fərqli tiplərdə və ya fərqli sayda ola bilər. Məsələn, metodumuz həm insanı, həm də heyvanı salamlamalıdır, ya da iki, üç, hətta on ədəd tam ədədi toplamalıdır.
Əlbəttə, metodları fərqli adlarla adlandıra bilərik: SumTwo, SumThree, SumArray. Amma proqramçılar tənbəldir (boşuna demirlər ki, tənbəllik tərəqqinin mühərrikidir). Üstəlik, belə kod oxunmaz olacaq.
Metodun overload olunması
Metodların overload olunması — bu, eyni adda metodu fərqli parametrlərlə işləməyə "məcbur etmək" üsuludur, metodun adı dəyişmir. Bu, polimorfizmin bir növüdür, amma inheritance ilə bağlı deyil.
Metodun overload olunması — bu, bir class-da (və ya struct-da) eyni adda bir neçə metod yaratmaq imkanıdır, amma parametrlərin siyahısı (tipi, sayı və/və ya ardıcıllığı) fərqli olur.
Metodun signaturası
Signatura — C#-da metodun adı üstəgəl parametrlərin tipi və ardıcıllığıdır. Metodun qaytardığı tip signaturaya daxil deyil! Bu, tez-tez gözlənilməz səhvlərə səbəb olur (bu barədə bir azdan danışacam).
2. Overload işdə: sadə nümunələr
Gəlin Greeter adlı bir class yaradaq, hansı ki, istifadəçiləri müxtəlif cür salamlayacaq: sadəcə adla, ad və yaşla, ya da ümumiyyətlə parametr olmadan.
public class Greeter
{
// Parametrsiz salamlaşma
public void Greet()
{
Console.WriteLine("Salam, dünya!");
}
// Adla salamlaşma
public void Greet(string name)
{
Console.WriteLine($"Salam, {name}!");
}
// Ad və yaşla salamlaşma
public void Greet(string name, int age)
{
Console.WriteLine($"Salam, {name}! Sən artıq {age} yaşındasan? Pis deyil!");
}
}
İndi bu metodlardan istənilən birini çağıra bilərik və C# kompilyatoru özü lazım olan variantı seçəcək — ötürülən parametrlərin sayına və tipinə baxaraq.
var greeter = new Greeter();
greeter.Greet(); // Salam, dünya!
greeter.Greet("Anya"); // Salam, Anya!
greeter.Greet("Pyotr", 23); // Salam, Pyotr! Sən artıq 23 yaşındasan? Pis deyil!
3. Tipə və parametr sayına görə fərqləndirmə
Overload işləyir, əgər metodlar fərqlənirsə:
- parametrlərin sayı ilə,
- heç olmasa bir parametrin tipi ilə,
- parametrlərin tiplərinin ardıcıllığı ilə (amma burada diqqətli olmaq lazımdır).
Gəlin təkcə yaş qəbul edən bir overload əlavə edək:
public void Greet(int age)
{
Console.WriteLine($"Bu qədər yaş — superdir! ({age} yaş)");
}
İndi çağırışlar:
greeter.Greet(10); // Bu qədər yaş — superdir! (10 yaş)
Vacibdir yadında saxla: əgər metodlar yalnız qaytardığı tipə görə fərqlənirsə, onları overload etmək olmaz. Məsələn, belə kod səhv verəcək:
// Kompilyasiya səhvi!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
Kompilyator deyəcək: "Artıq Foo(string) metodu müəyyən olunub, bir az daha çətin bir şey elə!"
4. Overload və C# standart kitabxanası
Overload — təkcə bizim uydurduğumuz Greet deyil. Gəlin .NET üçün Console.WriteLine sənədinə baxaq:
| Signatura | Təyinatı |
|---|---|
|
Boş sətri çap edir |
|
Sətir çap edir |
|
Tam ədəd çap edir |
|
Ondalık ədəd çap edir |
|
Bir arqumentlə sətri formatlayır |
|
Birkaç arqumentlə formatlayır |
Bunların hamısı eyni metodun overload-larıdır — WriteLine. İndi başa düşürsən ki, niyə həmişə belə yaza bilirsən:
Console.WriteLine("Sadəcə sətir");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("Cəm: {0}", 42);
Və kompilyator həmişə sənin çağırışını düzgün başa düşəcək!
5. Kompilyator hansı overload-u çağıracağını necə seçir?
Burada hər şey dəqiqdir: o, faktiki ötürülən arqumentlərin tipinə və sayına baxır. Kiçik bir cədvəl:
| Çağırış | Hansı versiya işləyəcək? |
|---|---|
|
|
|
|
Bəs qeyri-müəyyənlik olanda nə baş verir?
Bəzən vəziyyət nəzarətdən çıxır. Qeyri-müəyyən overload nümunəsi — kompilyator düzgün versiyanı seçə bilmir
public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }
printer.Print(5, 10);
// Səhv: qeyri-müəyyənlik — hansı Print çağırılsın? (hər ikisi uyğun gəlir kimi)
Kompilyator qeyri-müəyyənlik səhvi verəcək. Belə hallarda, kompilyatoru çaşdıra biləcək eyni sayda və oxşar tipli parametrlərlə overload-lardan qaçmaq daha yaxşıdır.
6. params — dəyişkən sayda parametrlər
Tutaq ki, sən elə bir metod yazmaq istəyirsən ki, qeyri-müəyyən sayda ədəd qəbul etsin. Burada sənə params açar sözü kömək edəcək.
public void SumAll(params int[] numbers)
{
int sum = 0;
foreach (int n in numbers)
sum += n;
Console.WriteLine($"Cəm: {sum}");
}
İndi belə çağırmaq olar:
SumAll(1, 2, 3); // Cəm: 6
SumAll(10, 20); // Cəm: 30
SumAll(); // Cəm: 0
params ilə metodları overload ilə birləşdirmək olar, amma əsas odur ki, kompilyatorun hansı versiyanı nəzərdə tutduğunu dəqiq başa düşməsinə mane olan overload-lar etməyəsən.
7. Overload və parametr modifikatorları (ref, out, in)
C# metodları parametr modifikatorlarına görə fərqləndirir (yəni, void Foo(int a) ilə void Foo(ref int a) signaturası fərqlidir və hər ikisi bir class-da ola bilər):
public void SetValue(int a)
{
a = 42;
}
public void SetValue(ref int a)
{
a = 100;
}
ref olmadan çağırış birinci versiyaya düşəcək, ref ilə isə ikinciyə:
int n = 5;
SetValue(n); // n olduğu kimi qalır (dəyər kopyalanır)
SetValue(ref n); // n olur 100
8. Sxem: overload nədir
+----------+
| MyClass |
+----------+
|
| (metodların fraqmenti)
+-----------------------+
| void Foo() |
| void Foo(int a) |
| void Foo(string s) |
| void Foo(int a, int b) |
+-----------------------+
Koddakı kimi təsəvvür etsək:
// Foo() metodunun overload versiyalarını çağırırıq:
var mc = new MyClass();
mc.Foo(); // void Foo()
mc.Foo(5); // void Foo(int)
mc.Foo("Hello"); // void Foo(string)
mc.Foo(2, 3); // void Foo(int, int)
9. Nümunə: tətbiqimizdə metodları overload edirik
Gəlin tədris tətbiqimizi inkişaf etdirək, heyvanlar iyerarxiyasına metod overload əlavə edək.
public class Animal
{
public string Name { get; set; }
// Səs çıxarmaq üçün metod
public virtual void MakeSound()
{
Console.WriteLine("Naməlum bir səs...");
}
// Overload olunmuş metod: səsin səviyyəsi ilə
public void MakeSound(int volume)
{
Console.WriteLine($"Heyvan {volume} dB səviyyəsində səs çıxarır.");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Hav!");
}
// Overload olunmuş metod: havlama səsi ilə səviyyə
public void MakeSound(int volume)
{
Console.WriteLine($"Hav! (səviyyə: {volume} dB)");
}
}
Belə çağırışları yoxla:
Dog rex = new Dog();
rex.MakeSound(); // Hav!
rex.MakeSound(75); // Hav! (səviyyə: 75 dB)
Diqqət yetir: child class-da (Dog) biz MakeSound(int volume) metodunu overload etdik və indi hər iki versiya var: parametrli və parametrsiz.
10. Metodların overload olunmasında tipik səhvlər
Səhv №1: yalnız qaytardığı tipə görə overload etməyə cəhd.
Bu mümkün deyil — qaytarılan tip metodun signaturasına daxil deyil. Overload fərqli sayda və ya tipdə giriş parametrləri ilə olmalıdır, void və ya int ilə yox.
Səhv №2: kompilyatoru çaşdıran qeyri-müəyyən overload-lar.
Eyni sayda və oxşar tipli overload-lar (məsələn, int və double) kompilyatoru çaşdıra bilər. Nümunə: Print(int a, double b) və Print(double a, int b) — Print(1, 1) çağırışı qeyri-müəyyənlik səhvi verəcək.
Səhv №3: params ilə digər overload-ların konflikti.
params ilə metod başqa overload üçün nəzərdə tutulmuş çağırışı "ələ keçirə" bilər. Əgər tiplər üst-üstə düşürsə, kompilyator gözləmədiyin metodu seçə bilər.
Səhv №4: ref və out signaturaya daxil olduğunu unutmaq.
Do(ref int x) və Do(out int x) metodları fərqli overload sayılır. Bunu nəzərə almasan, asanlıqla səhv versiyanı çağıra bilərsən.
GO TO FULL VERSION