🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Delegaty w C# — czym są i jak działają

Delegat to typ, którego zmienna nie przechowuje liczby ani obiektu — przechowuje odwołanie do metody. Skoro zmienna wskazuje na metodę, można ją wywołać dokładnie tak, jak samą metodę: nawiasami, opcjonalnie z argumentami. To fundament, na którym zbudowane są zdarzenia (events) oraz generyczne delegaty Func i Action — ten artykuł skupia się na samym słowie kluczowym delegate i własnych, nazwanych typach delegatów.

Deklaracja własnego typu delegata

Podobnie jak słowo kluczowe class tworzy typ opisujący dane i zachowanie, słowo kluczowe delegate tworzy typ opisujący sygnaturę metody — jaki zwraca wynik i jakie przyjmuje parametry. Każda metoda o pasującej sygnaturze może zostać przypisana do zmiennej tego typu, niezależnie od jej nazwy.

// Deklaracja typu delegata — metody void, przyjmujące jeden string
public delegate void Writer(string message);

To nie jest wywołanie metody — nie ma tu nawiasów klamrowych ani ciała. To definicja typu, dokładnie jak definicja klasy, tylko krótsza. Teraz możemy stworzyć zmienną tego typu i przypisać jej dowolną metodę o zgodnej sygnaturze:

public class Logger
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"[LOG] {message}");
    }
}

var logger = new Logger();

// Tworzymy delegat i przypisujemy mu metodę WriteMessage
Writer writer = logger.WriteMessage;

// Wywołujemy delegat — to wywołuje logger.WriteMessage("...") pod spodem
writer("Aplikacja uruchomiona");
// [LOG] Aplikacja uruchomiona

Kluczowa rzecz do zapamiętania: logger.WriteMessage bez nawiasów nie wywołuje metody — to przekazanie odwołania do niej. Wywołanie następuje dopiero, gdy użyjesz zmiennej writer z nawiasami. Nazwa metody (WriteMessage) nie musi się niczym wiązać z nazwą typu delegata (Writer) — liczy się wyłącznie zgodność sygnatury: taki sam typ zwracany i takie same parametry.

Delegaty multicast — łączenie wielu metod

To, czego nie robią pojedyncze wywołania metod, a robią delegaty: można połączyć wiele metod w jednej zmiennej operatorem +=, a wywołanie zmiennej uruchomi wszystkie po kolei:

Writer writer = logger.WriteMessage;
writer += message => File.AppendAllText("log.txt", message + Environment.NewLine);
writer += message => Console.Beep();

writer("Krytyczny błąd systemu");
// wykonuje WriteMessage, potem zapis do pliku, potem sygnał dźwiękowy

To właśnie mechanizm multicast stoi za zdarzeniami z wieloma subskrybentami — event to w gruncie rzeczy delegat multicast z ograniczonym dostępem z zewnątrz klasy (szczegóły w artykule o events). Operator -= działa symetrycznie i usuwa metodę z listy wywołań.

Pułapka: jeśli delegat multicast zwraca wartość (nie void), wywołanie zwróci tylko wynik ostatniej wywołanej metody — wyniki pośrednie są tracone. Z tego powodu delegaty multicast używa się niemal wyłącznie z metodami void (jak w zdarzeniach).

Czego Func/Action nie potrafią, a własny delegat tak

Skoro .NET ma gotowe, generyczne Func<> i Action<>, po co w ogóle definiować własny typ delegata? Dwa konkretne powody:

  • Parametry ref/out/in — Func i Action nie obsługują modyfikatorów refout ani in. Jeśli metoda musi przyjmować parametr przez referencję, potrzebujesz własnego typu delegata.
// Func/Action nie da się tak zapisać — trzeba własnego delegata
public delegate bool TryParser<T>(string input, out T result);
  • Nazwana, domenowa intencja — public delegate void OrderProcessed(Order order); czyta się i dokumentuje lepiej niż Action<Order> rozsiane po całym kodzie. Nazwany typ pojawia się też czytelnie w IntelliSense i sygnaturach metod publicznego API biblioteki.

Poza tymi dwoma przypadkami współczesny C# niemal zawsze preferuje gotowe Func/Action — mniej kodu do utrzymania, ten sam efekt. Własny delegate to dziś raczej świadomy wyjątek niż domyślny wybór.

Delegat jako parametr metody

Najczęstsze praktyczne zastosowanie delegata — przekazanie zachowania jako argumentu, żeby metoda ogólna mogła wywołać konkretną logikę dostarczoną przez wywołującego:

public delegate bool Predicate(int number);

static List<int> Filter(List<int> numbers, Predicate condition)
{
    var result = new List<int>();

    foreach (var number in numbers)
    {
        if (condition(number))
        {
            result.Add(number);
        }
    }

    return result;
}

var parzyste = Filter(new List<int> { 1, 2, 3, 4, 5, 6 }, n => n % 2 == 0);
// [2, 4, 6]

To dokładnie ten sam wzorzec, na którym opiera się List<T>.Where(Func<T, bool> predicate) w LINQ — tam .NET używa gotowego Func zamiast własnego typu, bo nie potrzebuje ref/out ani domenowej nazwy.

FAQ

Czy delegat może wskazywać na metodę statyczną?
Tak — składnia jest identyczna, np. Writer writer = Logger.StatycznaWriteMessage;, bez potrzeby instancji obiektu.

Co się stanie, gdy wywołam pusty (null) delegat?
NullReferenceException — dokładnie tak samo jak w przypadku zdarzeń. Sprawdzaj null przed wywołaniem albo użyj operatora ?.Invoke(...).

Czy Func/Action to też delegaty?
Tak — to gotowe, generyczne typy delegatów dostarczone przez .NET, żeby nie trzeba było deklarować własnych dla najczęstszych sygnatur. Pełne omówienie w artykule o Func i Action.

Jak to się ma do wzorców projektowych?
Delegaty to fundament, na którym C# buduje Observer Patternevent to w praktyce gotowy, wbudowany w język Observer, bez ręcznie pisanej listy obserwatorów i interfejsu.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

2 comments

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →