1. Giriş
İndiyə kimi siz gördünüz: biz LINQ metodlarını çağırırdıq, hansı ki lambdda qəbul edirdi. Amma C# metodu necə anlayır ki, ona funksiyanı ötürmək olar?
Sadə desək, delegatlar funksiyalar üçün "interfeys" kimidir: biz imzanı (parametrlərin tipləri və geri qaytarılan dəyər) təsvir edirik və bu imzaya uyğun hər hansı bir metod (və ya lambdda!) o yerlərə ötürülə bilər ki, orada həmin delegat gözlənilir.
Xatırlayın, siz necə string-i parametr kimi ötürürdünüz? Təxminən eyni şəkildə “loqika” ötürülür, yalnız parametr tipi — delegat olur.
Delegatlar: insan dilində əsas nəzəriyyə
C#-da delegat — bu “belə imzalı funksiya”nı təsvir edən tipdir.
// Delegat, int qəbul edir və bool qaytarır
public delegate bool IntPredicate(int x);
İmzası uyğun olan hər hansı bir funksiya bu tipdə dəyişənə təyin oluna bilər:
bool IsEven(int n) => n % 2 == 0;
IntPredicate pred = IsEven;
Və indi — lambdda uyğun gəlir:
IntPredicate pred = x => x % 2 == 0;
Universal delegatlar: Func, Action, Predicate
- Func<T1, ..., TResult> — T1, ... parametrlərini qəbul edən və TResult-u qaytaran funksiya.
- Action<T1, ...> — parametrlər qəbul edən və dəyər qaytarmayan funksiya (void).
- Predicate<T> — T qəbul edən və bool qaytaran funksiya.
2. Lambdda-nı öz metodumuza ötürmə
Tutaq ki, biz dərs üçün kiçik konsol tətbiqimizi inkişaf etdiririk — istifadəçilər siyahısı ilə işləyən console layihəsi. Əvvəl kolleksiyaları LINQ ilə filtrləyirdik, indi isə lambdda-şərt qəbul edən öz metodumuzu yazacağıq.
Lambdda-parametri olan metod yaradırıq
// Nümunə üçün User sinifini təyin edək (tətbiqimizə əlavə edirik)
public class User
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
// Siyahını və delegat-şərti (lambdda) qəbul edən metod
public static List<User> FilterUsers(List<User> users, Predicate<User> predicate)
{
var result = new List<User>();
foreach (var user in users)
{
if (predicate(user)) // Lambdda çağırılır!
result.Add(user);
}
return result;
}
İndi hər hansı lambdda ötürmək olar:
var users = new List<User>
{
new User { Name = "Vasya", IsActive = true },
new User { Name = "Petya", IsActive = false },
new User { Name = "Masha", IsActive = true }
};
// Yalnız aktivləri filtr edirik
var activeUsers = FilterUsers(users, user => user.IsActive);
foreach (var user in activeUsers)
Console.WriteLine(user.Name); // Vasya, Masha
Beləliklə! Biz loqika parçasını — kiçik funksiyanı — adi parametr kimi ötürdük, çünki FilterUsers Predicate<User> gözləyir və biz ona uyğun lambdda verdik.
Func<T, TResult> ilə variant
Predicate<T> şərt üçün uyğundur (geri qaytarılan bool). Amma əgər hər bir istifadəçi üçün nəsə “hesablamaq” istəyiriksə?
// Hər elementə funksiya tətbiq edib nəticələri toplayan metod
public static List<TResult> MapUsers<TResult>(List<User> users, Func<User, TResult> selector)
{
var result = new List<TResult>();
foreach (var user in users)
{
result.Add(selector(user));
}
return result;
}
İstifadə:
var names = MapUsers(users, user => user.Name.ToUpper());
foreach (var name in names)
Console.WriteLine(name); // VASYA, PETYA, MASHA
3. Faydalı nüanslar
Müxtəlif ötürmə formaları
Lambdda ilə yanaşı adi metodu da ötürmək olar — əsas odur ki, imza uyğun olsun.
// Adi metod
static bool NameHasS(User user) => user.Name.Contains("s");
// Adi metodun ötürülməsi:
var usersWithS = FilterUsers(users, NameHasS);
// Lambddanın ötürülməsi
var usersWithA = FilterUsers(users, u => u.Name.Contains("a"));
Həmçinin köhnə tip anonim metod da mümkündür (amma buna ehtiyac yoxdur):
var usersWithM = FilterUsers(users, delegate(User u) { return u.Name.Contains("m"); });
Müasir stil — lambddalardır!
LINQ-də lambdda ötürülməsi: arxada nə baş verir
var result = users.Where(u => u.IsActive).ToList();
Arxa planda Where Func<User, bool> qəbul edir. Bu o deməkdir ki, hər hansı metod ki, Func<...> qəbul edirsə, eyni cür istifadə etmək olar!
İki parametr istəyirsinizsə?
// Metod iki lambdda qəbul edir filtr üçün
public static List<User> FilterUsersCustom(
List<User> users,
Func<User, bool> include,
Func<User, bool> exclude)
{
var result = new List<User>();
foreach (var user in users)
{
if (include(user) && !exclude(user))
result.Add(user);
}
return result;
}
İstifadə:
var customFiltered = FilterUsersCustom(
users,
u => u.Name.StartsWith("V"),
u => u.IsActive == false
);
// Yalnız adı "V" ilə başlayan və aktiv olan istifadəçiləri götürəcək
Ssenari: Filtrlər fabriki
Console.WriteLine("Adın minimal uzunluğunu daxil edin:");
int minLength = int.Parse(Console.ReadLine());
Predicate<User> lengthFilter = user => user.Name.Length >= minLength;
var filteredUsers = FilterUsers(users, lengthFilter);
// Çox interaktiv və canlı!
4. Tipik səhvlər və nüanslar
Bəzən kompilyator lambdda parametrlərinin tipini “çıxaara” bilmir — xüsusən overload-ların olduğu mürəkkəb səhnarilərdə və ya metod konkret geri qaytarma tipli delegat tələb etdikdə. Belə hallarda lambdda tiplərini açıq göstərmək olar:
FilterUsers(users, (User u) => u.Name.Length > 3);
və ya hətta:
MapUsers(users, (User u) => u.Name.ToUpper());
Səhv: lambdda imzaya uyğun gəlmir
FilterUsers(users, user => Console.WriteLine(user.Name)); // səhv! bool gözlənilir, void alınır
Çünki bool qaytaran funksiya gözlənilir, amma lambdda void qaytarır (dəqiq desək, heç nə qaytarmır). Geri qaytarılan tipə diqqətli olun!
Səhv: lambddalardan artıq istifadə
Əgər lambddalar 10 sətrə qədər uzanırsa, onları ayrıca metoda çıxarın. Bu həm oxunaqlıdır, həm də debugging üçün daha rahatdır.
GO TO FULL VERSION