1. Giriş
Məktəbdə sizə bəlkə deyiblər: "Bu funksiyanın funksiyasıdır". Yüksək səviyyəli funksiyalar (Higher-Order Functions, HOF) — ya funksiyaları arqument kimi qəbul edən, ya funksiyaları nəticə kimi qaytaran, ya da həm qəbul edib həm qaytaran funksiyalardır.
Sadə dildə: əgər metodunuz başqa bir funksiyanı (məsələn, delegat və ya lambda) parametr kimi ala bilirsə və ya onu nəticə kimi qaytara bilirsə — təbriklər, sizin funksiyanız yüksək səviyyəlidir!
Yüksək səviyyəli funksiya elədir ki, o təkcə qapını aça bilmir, həm də sizə başqa bir açar verə bilər ki, sonra lazım olan qapını açasınız.
Gündəlik həyatda tətbiqi
Bu hamısı maraqlı və qeyri-trivial səslənir. Amma niyə C# inkişaf etdiricisi üçün bu fəndlər gündəlik işdə lazımdır? Ən qısa cavab budur:
Yüksək səviyyəli funksiyalar kodu elastik, reusable və qısa edir.
Bu əsasdır belə şeylər üçün: LINQ, kolleksiyaların filtrə edilməsi və sıralanması, data pipeline-ların qurulması, callback və event konfiqurasiyası, hətta dependency injection-də bəzi nümunələr.
Bir neçə real ssenari:
- Metodun davranışını məhdudlaşdırmaq üçün ora logic ötürmək (məsələn, filtr, sort, transformasiya).
- Müxtəlif əməliyyatları delegat vasitəsilə "vurub" universal data handler yazmaq.
- Hər bir funksiyanın öz reseptinə görə datanı dəyişdirdiyi zəncirlər ("pipeline") qurmaq.
- Kross-platform abstraksiyalar yaratmaq: Windows-da nə etmək, Linux-da nə? Sadəcə uyğun funksiyanı ötür.
2. Sadə yüksək səviyyəli funksiyalar nümunələri
Başqa funksiyanı qəbul edən funksiya
Ən klassik nümunə — delegat və ya lambda qəbul edən metod.
// Yüksək səviyyəli funksiya: process funksiyasını parametr kimi qəbul edir
void ForEach<T>(IEnumerable<T> collection, Action<T> process)
{
foreach (var item in collection)
{
process(item); // Argument funksiyanı çağırırıq
}
}
// İstifadəsi:
var numbers = new List<int> { 1, 2, 3 };
ForEach(numbers, n => Console.WriteLine($"Element: {n}"));
Burada nə baş verir? ForEach hər elementlə nə edəcəyini bilmir. O yalnız verilmiş processor-u (process) çağırır. Bu processor istənilən şey ola bilər — ekrana yazmaq, baza saxlamaq, UI-da render etmək və s.
Bəli, C# kolleksiyalarındakı ForEach metodu belə işləyir və demək olar ki bütün LINQ metodları yüksək səviyyəli funksiyalardır!
Funksiya, funksiyanı qaytaran
İndi bir az ağır nümunə — metod həm qəbul edir, həm də funksiyanı qaytara bilər.
// Yüksək səviyyəli funksiya: başqa funksiya qaytarır
Func<int, int> CreateMultiplier(int factor)
{
// factor dəyişənini istifadə edən lambda qaytarırıq
return x => x * factor;
}
// İstifadəsi:
var multiplyBy10 = CreateMultiplier(10);
Console.WriteLine(multiplyBy10(7)); // 70
Burada CreateMultiplier əvvəlcədən təyin olunmuş faktorla arqumentini vuracaq funksiyanı qaytarır. Bu artıq "funksiya fabriki" nümunəsidir.
Funksiya həm qəbul edən, həm qaytaran
Func<int, int> Compose(Func<int, int> f, Func<int, int> g)
{
// f(g(x)) tətbiq edən funksiyanı qaytaracaq
return x => f(g(x));
}
// İstifadəsi:
Func<int, int> increment = x => x + 1;
Func<int, int> doubleIt = x => x * 2;
var incrementThenDouble = Compose(doubleIt, increment);
Console.WriteLine(incrementThenDouble(5)); // (5 + 1) * 2 = 12
Elə bu cür kompozisiyalar data axını emalının əsasını təşkil edir — məsələn, Select, Where, OrderBy kimi metodlarla massivləri emal edərkən.
3. C# necə yüksək səviyyəli funksiyaları dəstəkləyir
Funksional dillərdə (Haskell, F#) bütün funksiyalar default olaraq yüksək səviyyəlidir. Amma C# (2.0-dan bəri) bu yanaşmanı delegatlar və lambda-lar vasitəsilə dəstəkləyir.
- Delegatlar (Func, Action, Predicate) — funksiya tipləridir.
- Lambda ifadələri — funksiyaları yerində yaratmaq üçün sintaksisdir.
- Metodlar delegat qəbul edib qaytara bilər — deməli yüksək səviyyəli funksiyalar "qutudan" dəstəklənir.
Vizual sxem
flowchart LR
A[Data] --> B[Funksiya 1]
B --> C[Funksiya 2]
C --> D[Nəticə]
subgraph "Emal pipeline-ı (yüksək səviyyəli funksiyalar)"
B
C
end
4. Tətbiqimizi inkişaf etdiririk
Gəlin bizim demo app-ı davam etdirək — kiçik "String processor" olsun.
Sadə yüksək səviyyəli metod əlavə edək
Tutaq ki, istifadəçi adlarının siyahısı var və biz onları müxtəlif funksiyalarla istənilən şəkildə dəyişmək istəyirik.
// Strings siyahısını və transformasiya funksiyasını qəbul edən metod
List<string> TransformNames(List<string> names, Func<string, string> transformer)
{
var result = new List<string>();
foreach (var name in names)
{
result.Add(transformer(name));
}
return result;
}
Bu metodu necə istifadə etmək olar?
var names = new List<string> { "Anna", "Boris", "Sergey" };
// Böyük hərflərə çevir
var upperNames = TransformNames(names, n => n.ToUpper());
// Hər ada "cənab/cənabə" əlavə edək
var politeNames = TransformNames(names, n => "Hörmətli(ə) " + n);
foreach (var n in upperNames)
Console.WriteLine(n); // ANNA, BORIS, SERGEY
foreach (var n in politeNames)
Console.WriteLine(n); // Hörmətli(ə) Anna, ...
TransformNames universal-dir: o transformasiya məntiqini parametrə (transformer) həvalə edir — məsələn, ToUpper çağırışı və ya başqa hər hansı resept.
Tipə uyğunlaşma
Bu nümunəni asanlıqla istənilən tipə uyğunlaşdırmaq olar.
// Generic universal metod - istənilən T ilə işləyən yüksək səviyyəli funksiya
List<TResult> Map<T, TResult>(List<T> items, Func<T, TResult> transformer)
{
var result = new List<TResult>();
foreach (var item in items)
{
result.Add(transformer(item));
}
return result;
}
Tətbiqi:
var numbers = new List<int> { 1, 2, 3 };
var doubled = Map(numbers, x => x * 2); // [2, 4, 6]
var strings = Map(numbers, x => $"Sayı: {x}"); // ["Sayı: 1", ...]
5. Filtrləmə və aggregasiya yüksək səviyyəli funksiyalar vasitəsilə
Filtrləmə və axtarış məntiqi çoxdan yüksək səviyyəli funksiyalarla həyata keçirilir.
// Filtrləmə: yüksək səviyyəli funksiya
List<T> Filter<T>(List<T> items, Predicate<T> criteria)
{
var result = new List<T>();
foreach (var item in items)
{
if (criteria(item)) // Kriteriya funksiyasını çağırırıq
{
result.Add(item);
}
}
return result;
}
Necə istifadə olunur:
var names = new List<string> { "Anna", "Boris", "Andrey" };
var aNames = Filter(names, n => n.StartsWith("A"));
// Nəticə: "Anna", "Andrey"
6. Funksiyaların kompozisiyası (function composition)
Yüksək səviyyəli funksiyalar tək bir funksiyanı istifadə etməyə yox, onları zəncirləyib kompozisiya etməyə imkan verir. C#-də bunu iki funksiyanı qəbul edib onların birləşmişini qaytaran funksiya kimi həyata keçirmək olar.
// Funksiya kompozitoru: əvvəlcə g, sonra f tətbiq edən funksiyanı qaytarır
Func<T, TResult> Compose<T, TIntermediate, TResult>(
Func<TIntermediate, TResult> f,
Func<T, TIntermediate> g)
{
return x => f(g(x));
}
// Nümunə:
Func<int, int> plusOne = n => n + 1;
Func<int, int> timesTwo = n => n * 2;
var plusOneThenDouble = Compose(timesTwo, plusOne);
Console.WriteLine(plusOneThenDouble(3)); // (3 + 1) * 2 = 8
7. Faydalı nüanslar
Problemin əvvəlki tarixi: niyə əvvəllər hər şey daha çətin idi?
Delegatlar və lambda-lar ortaya çıxana qədər developer-lər çox oxşar loop-lar yazır, kod fraqmentlərini kopyalayırdılar ki, datanı "filter", "transform" və ya "group" etsinlər. Yüksək səviyyəli funksiyalarla davranışın dəyişkən hissəsini funksiyalar-parametrə ayırmaq mümkün oldu — bu da duplication-u kəskin azaldıb kodun ifadəliliyini artırdı.
Sintaksik şəkər: funksiyalar ifadə kimi
Yüksək səviyyəli funksiyalar tez-tez expression-bodied metodlar vasitəsilə qısa şəkildə yazılır:
List<string> FilterNames(Predicate<string> pred) =>
Names.Where(name => pred(name)).ToList();
List<TResult> MapNames<TResult>(Func<string, TResult> transformer) =>
Names.Select(transformer).ToList();
LINQ mexanizmləri ilə müqayisə
Gəlin baxaq, LINQ necə yüksək səviyyəli funksiyalardan istifadə edir:
| LINQ metodu | Hansı delegatı qəbul edir | Təyinat |
|---|---|---|
|
|
Elementləri filtr edir |
|
|
Elementləri transformasiya edir |
|
|
Açar üzrə sıralayır |
|
|
Kolleksiyanı aggregate (reduce) edir |
|
|
Şərtə cavab verən elementin olub-olmadığını yoxlayır |
Bütün bu metodlar yüksək səviyyəli funksiyalar ideyasına əsaslanır: siz işləmə qaydalarını yazırsınız, standart kitabxana isə "infrastruktur" təmin edir.
8. Mümkün səhvlər və tələflər
Delegat tipləri ilə qarışıqlıq.
Əvvəlcə Action, Func və Predicate-in nə vaxt lazım olduğunu başa düşmək çətin ola bilər.
Məsləhət: Əgər funksiya bool qaytarır — çox güman ki, Predicate. Əgər dəyər qaytarırsa — Func, əgər heç nə qaytarmırsa — Action.
Variable capture (closures).
Əgər qaytarılan funksiya xarici mühitdən dəyişənlərdən istifadə edirsə, çağırış anında dəyərlərin doğru olmasına diqqət et. Dəyişənlər kopyalanmır, onlar "captured" olunur.
Mürəkkəb zəncirlərin debugging-i.
Funksiyalar uzun pipeline-larda birləşəndə hansı layer-in məlumatı səhv emal etdiyini anlamaq çətinləşir. Müvəqqəti yazılar və şərhlər əlavə edin:
n => {
Console.WriteLine("Əməliyyatdan əvvəl:" + n);
var res = n * 2;
Console.WriteLine("Əməliyyatdan sonra:" + res);
return res;
}
GO TO FULL VERSION