1. Callback və asinxron proqramlaşdırma
Callback (callback) — bir əməliyyat tamamlandıqda çağırılmalı olan metodun ötürülməsi mexanizmidir. Tez-tez asinxron əməliyyatlarda, timerlərdə, məlumatların emalında və UI-da istifadə olunur.
Misal 1: Callback ilə asinxron əməliyyat
Tutaq ki, istifadəçi sorğu daxil edir və nəticəni müəyyən gecikmə ilə (məsələn, internetdən) alırıq. Məlumat alındıqdan sonra ekranı yeniləmək istəyirik.
// Delegat geri çağırış üçün
public delegate void DataReceivedHandler(string result);
// Məlumatı asinxron yükləmə mexanizmi (imitasiya)
public void DownloadDataAsync(DataReceivedHandler callback)
{
// Tutaq ki, yükləmə vaxt aparır (timer ilə imitasiya)
Task.Delay(1000).ContinueWith(_ =>
{
string data = "Axtarış nəticələri: <məlumat>";
callback(data); // Callback delegatın çağırılması
});
}
// İstifadə:
DownloadDataAsync(result =>
{
Console.WriteLine("Alındı: " + result);
});
Bu yanaşma nəticə alındıqdan sonra məntiqi tamamilə məlumat əldə etmə mexanikasından ayırmağa imkan verir və kodu çevik edir.
2. Delegatlar metod parametri kimi: strategiya və comparatorlar
Tez-tez olan tapşırıq: istifadəçiyə sizə metodda “məntiq” (funksiya) ötürməsinə imkan vermək, beləliklə o öz elementləri necə müqayisə etməyi, filtr etməyi və ya çevirməyi təyin edə bilsin.
Misal 2: Delegatlar vasitəsilə Strategy pattern-in tətbiqi
Tutaq ki, bizdə sortlama var, amma istifadəçi müxtəlif şəkildə — ada, tarixə, ölçüyə görə və s. sortlamağı seçə bilsin.
public delegate bool CompareFunc(int a, int b);
public void BubbleSort(int[] arr, CompareFunc compare)
{
for (int i = 0; i < arr.Length; i++)
{
for (int j = 0; j < arr.Length - 1; j++)
{
if (compare(arr[j], arr[j + 1]))
{
// Yerini dəyişirik
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// Böyükdən kiçiyə sortlama
CompareFunc descending = (a, b) => a < b;
// İstifadə
int[] numbers = { 3, 1, 4, 2 };
BubbleSort(numbers, descending);
Console.WriteLine(string.Join(", ", numbers)); // Çıxış: 4, 3, 2, 1
Belə yanaşma — başqa birinin kodunu dəyişmədən öz “strategiyanı” yeridə bilmək üçün universal üsuldur.
3. Anonim metodlar, lambda-ifadələr və delegatlar
C# inkişaf etdikcə hər tapşırıq üçün ayrıca klass və ya metod yazmaq əlverişsiz oldu. Şükürlər olsun ki, anonim metodlar və lambda-ifadələr delegatları "anında" yaratmağa imkan verir.
Misal 3: Lambda delegat kimi
Func<int, int, int> operation = (x, y) => x * y;
int result = operation(3, 5); // 15
Misal 4: Əməliyyatı ada görə seçmək (switch + Delegates)
Func<int, int, int> op;
string userInput = "sum"; // "sub", "mul", "div"
switch (userInput)
{
case "sum": op = (a, b) => a + b; break;
case "sub": op = (a, b) => a - b; break;
case "mul": op = (a, b) => a * b; break;
case "div": op = (a, b) => a / b; break;
default: throw new Exception("Naməlum əməliyyat!");
}
Console.WriteLine(op(6, 2));
Giriş verilənlərinin validasiyasını unutmayın — delegatlar burada çeviklik və oxunaqlılıq verir.
4. Delegatlar və emal zəncirləri (“zəhmət zənciri”)
Delegatlar multi-address (çoxadlı) dəstəyi olduğuna görə handler zəncirlərini asanlıqla qura bilərsiniz.
Misal 5: Filtrlər zənciri
Tutaq ki, bizdə bir sıra "filtrlər" var və onlar bir stringi emal etməlidirlər.
public delegate string StringFilter(string input);
string RemoveDigits(string input) => new string(input.Where(ch => !char.IsDigit(ch)).ToArray());
string ToUpper(string input) => input.ToUpper();
StringFilter filters = RemoveDigits;
filters += ToUpper;
// Delegat bütün filtrlər üzrə stringi keçir
string text = "Salam123";
foreach (StringFilter filter in filters.GetInvocationList())
{
text = filter(text);
}
Console.WriteLine(text); // Çıxış: "SALAM"
Vacib: əgər sadəcə filters(text) çağırsanız, nəticə yalnız son handler-dən qaytarılacaq, bütün zəncirdən yox! Əgər dəyərin "axışını" istəyirsinizsə, yuxarıda göstərildiyi kimi GetInvocationList() ilə açıq iterasiyadan istifadə edin.
5. Delegatlar davranışı uçuş zamanı dinamik bağlamaq üçün
Əvvəllər bir davranışı başqa ilə əvəz etmək üçün əlavə klasslar və interfacelər uydurmalı idik. Delegatlar və lambda-ifadələrlə "kiçik" polymorphism-in böyük hissəsini funksiyalarla ifadə etmək olar.
Misal 6: Dinamik əmrlə robot davranışı
public class Robot
{
public event Action<string>? OnCommandReceived;
public void ReceiveCommand(string command)
{
OnCommandReceived?.Invoke(command);
}
}
// İstifadə:
var robot = new Robot();
robot.OnCommandReceived += cmd => Console.WriteLine($"Robot icra edir: {cmd}");
robot.OnCommandReceived += cmd =>
{
if (cmd == "Yandır")
Console.WriteLine("Sistem yüklənir...");
};
// Sınaq
robot.ReceiveCommand("Yandır");
robot.ReceiveCommand("Irəli hərəkət et");
Belə yanaşma testlərdə, prototiplərdə, DI (dependency injection) konteynerlərində və biznes- məntiqini parametr kimi ötürməkdə geniş istifadə olunur.
6. Delegatlar vəziyyətə abunəlik üçün
Tutaq ki, bizim bir klassımız var və müəyyən vəziyyət saxlayırıq, və onun dəyişməsində bütün abunəçiləri xəbərdar etmək istəyirik. Delegatlar (və eventlər) ilə bu çox asandır.
Misal 7: Dəyişiklikə abunəlik olan klass
public class Notifier<T>
{
private T _value = default!;
public event Action<T>? ValueChanged;
public T Value
{
get => _value;
set
{
if (!Equals(_value, value))
{
_value = value;
ValueChanged?.Invoke(_value);
}
}
}
}
// İstifadə:
var intValue = new Notifier<int>();
intValue.ValueChanged += v => Console.WriteLine($"Yeni dəyər: {v}");
intValue.Value = 5; // Event tetiklenir
intValue.Value = 10;
Belə yanaşma demək olar ki, "minimal reaktiv proqramlaşdırmadır", MVVM, data binding və bir çox müasir UI-çərçivələrin əsasıdır.
7. Delegatlar, closures və leksik görünürlük sahəsi
Lambda-ifadələr və anonim metodlar əhatə edən kontekstdən dəyişənləri capture edə bilər (closure). Bu faydalıdır, amma bəzən gözlənilməz səhvlərə gətirir.
Misal 8: Dəyişənin capture edilməsi və "dövr tələsi"
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
actions[i] = () => Console.WriteLine(i);
}
foreach (var a in actions)
a(); // Üç dəfə 3 çap edəcək (!)
Niyə? Çünki closure eyni i dəyişəninə referans edir və dövrdən sonra onun qiyməti 3-ə bərabərdir. Əgər 0, 1, 2 dəyərlərini yadda saxlamaq lazımdırsa?
for (int i = 0; i < 3; i++)
{
int loopValue = i; // cari dəyəri "dondururuq"
actions[i] = () => Console.WriteLine(loopValue);
}
İndi kod gözlənildiyi kimi işləyir. Belə tələlər lambda-ifadələrdən istifadə edərkən yeni başlayanların ən çox rastlaşdığı səhvlərdəndir!
8. Delegatların birləşdirilməsi
Multicast delegatlar çağırışlar siyahısını saxlayır və siz handler-ları əlavə edə (+=) və ya çıxara (-=) bilərsiniz.
Xüsusiyyət: çıxarma referans və signaturaya görə aparılır
void Handler1() => Console.WriteLine("1");
void Handler2() => Console.WriteLine("2");
Action a = Handler1;
a += Handler2;
a -= Handler1; // Yalnız Handler2 qalacaq
a?.Invoke(); // "2" çap edəcək
Misal: handler-ların dinamik idarəsi
Action a = Handler1;
a += Handler1;
a -= Handler1; // İndi siyahıda BİR dənə Handler1 qalır!
9. Delegatlar genişlənmə və inversion of control (IoC)
Böyük tətbiqlərdə komponentlərin quraşdırmadan asılı olmayaraq xarici kodu "çağırması" tələb olunur. Delegatlar "extension", plugin və callback-ləri tight coupling olmadan yerləşdirməyə kömək edir.
Misal: Konstruktor daxilində davranışın injection-u
public class Greeter
{
private readonly Func<string> _getName;
public Greeter(Func<string> getName)
{
_getName = getName;
}
public void Greet() => Console.WriteLine($"Salam, {_getName()}!");
}
// Müxtəlif davranışların injection-u:
var greeter1 = new Greeter(() => "Anya");
var greeter2 = new Greeter(() => DateTime.Now.ToShortTimeString());
greeter1.Greet(); // "Salam, Anya!"
greeter2.Greet(); // "Salam, 14:35!"
Real həyatda bu yanaşma testləşdirilən və asan baxılan kod yazmaq üçün geniş istifadə olunur.
10. Faydalı nüanslar
Standart interfeyslərdə və LINQ-da delegatlar
Delegatlarla qarşılaşmaq labüddür, əgər LINQ, kolleksiyalar, asinxronluq ilə işləyirsinizsə.
- Bir çox metodlar, məsələn, List<T>.Find, Array.Sort, Where, Select delegatları (Func<T, bool>, Comparison<T> və s.) qəbul edir.
- LINQ metodları filtrasiya, transformasiya, agregasiya məntiqini əlavə klass yaratmadan ötürməyə imkan verir.
Misal: Ob'ektləri sortlamaq üçün comparator
var people = new[] { "İvan", "Mariya", "Petr" };
Array.Sort(people, (a, b) => a.Length.CompareTo(b.Length));
Console.WriteLine(string.Join(", ", people)); // İvan, Petr, Mariya
Delegatlar və currying (parameterlərin qismən tətbiqi)
Anonim metodlar/lambda-ifadələr vasitəsilə bir hissə parametrləri "sabitləyib" yeni funksiya əldə edə bilərsiniz.
Misal: Qismən tətbiq
Func<int, int, int> sum = (x, y) => x + y;
// Həmişə 10 əlavə edən funksiya yaradırıq
Func<int, int> add10 = y => sum(10, y);
Console.WriteLine(add10(5)); // 15
Delegatların müqayisəsi xüsusiyyəti
C#-da delegatları bərabərlik (==) üzrə müqayisə etmək olar, əgər onların invocation list-ləri eynidirsə.
void Handler1() { }
void Handler2() { }
Action a1 = Handler1;
Action a2 = Handler1;
Console.WriteLine(a1 == a2); // True
Action a3 = Handler1; a3 += Handler2;
Action a4 = Handler1; a4 += Handler2;
Console.WriteLine(a3 == a4); // True
Amma əgər delegat anonim metoda və ya lambda-ya əsaslanırsa — müqayisə nümunələrə görə aparılır.
Delegatların serializasiyası
Delegatları serializasiya etmək olar, amma yalnız onlara referans verən metodlar serializasiya olunan klasslarda təyin olunubsa və bütün tiplər mövcuddursa. .NET 8-dən başlayaraq BinaryFormatter defolt üzrə deaktiv edilib və köhnəlmiş sayılır; gələcək versiyalarda tamamilə silinə bilər. Prod-da delegatların serializasiyası praktik olaraq istifadə olunmur.
Delegatlar və eventlər: harda delegat, harda event?
- Delegat — çağırıla bilən tip/variabldır.
- Event (event) — delegata çıxışı məhdudlaşdırmaq üsuludur: xaricdən sadəcə abunə/abonelikdən çıxma (+=/-=) mümkündür, amma çağırmaq yalnız klass daxilindən olar.
- Event həmişə delegat tipində olur, amma hər delegat event deyil.
Necə tətbiq etmək olar? Əgər məntiqin "klassdan kənarda" müəyyən edilməsi istəyirsinizsə, delegatlardan istifadə edin. Əgər abunə/abonelik idarəsini və dəyişəni qorumaq lazımdırsa — event istifadə edin.
11. Delegatlarla işləyərkən tipik səhvlər
Səhv №1: Delegat ilə event-i qarışdırmaq.
Publika sahə-delegat (public Action MyAction;) əvəzinə event (public event Action MyAction;) istifadə etmək enkapsulyasiyanı poza bilər. Xarici kod təsadüfən və ya qəsdən bütün abunəçiləri yenidən yaza bilər (instance.MyAction = null;) və ya onları birbaşa çağıra bilər, bu da klassın məntiqini poza bilər.
Səhv №2: Multi-address delegatların qaytarılan dəyərləri ilə səhv işləmək.
Əgər delegat dəyər qaytarırsa (məsələn, Func<string, int>), adi çağırışda (myDelegate("test")) zəncirin yalnız sonuncu metodunun nəticəsi dönəcək. Bütün abunəçilərin nəticələrini almaq üçün çağırış siyahısına GetInvocationList() ilə iterate etmək lazımdır.
// Bütün abunəçilərin nəticələrini iterasiya ilə almaq nümunəsi
var list = myDelegate.GetInvocationList();
foreach (var d in list)
{
var r = ((Func<string, int>)d)("test");
Console.WriteLine(r);
}
Səhv №3: Dövr dəyişəninin closure tərəfindən capture edilməsi.
Yeni başlayanların klassik tələsi: dövr daxilində yaradılmış lambda iterator dəyişənini deyil, onun referansını capture edir.
// Yanlış: bütün actions son i dəyərini çap edəcək
for (int i = 0; i < 3; i++)
{
actions[i] = () => Console.WriteLine(i);
}
// Düzgün: hər iterasiya üçün lokal nüsxə yaradırıq
for (int i = 0; i < 3; i++)
{
int copy = i;
actions[i] = () => Console.WriteLine(copy);
}
Səhv №4: Memory leak-lərin yaranması.
Əgər instance metodu uzunömürlü obyektdəki delegata abunə olunur və sonra abonelikdən çıxmırsa, memory leak yaranır. Uzunömürlü obyekt abunəçiyə referans saxlayır və GC onu silə bilmir. Xüsusən life-cycle məhdudiyyəti olan klasslarda (məsələn, UI komponentlərində) abonelikdən çıxmağa diqqət edin.
GO TO FULL VERSION