CodeGym /Kurslar /C# SELF /Yüksək səviyyəli funksiyalar

Yüksək səviyyəli funksiyalar

C# SELF
Səviyyə , Dərs
Mövcuddur

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
Where
Func<T, bool>
Elementləri filtr edir
Select
Func<T, TResult>
Elementləri transformasiya edir
OrderBy
Func<T, TKey>
Açar üzrə sıralayır
Aggregate
Func<TAcc, T, TAcc>
Kolleksiyanı aggregate (reduce) edir
Any
Func<T, bool>
Şə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;
}
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION