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

Delegat z parametrem w C# – typ jest kontraktem, nazwa nie

Delegat z parametrem w C# — dopasowanie sygnatury metody do delegata
private static void ShowHello(string name)
{
    Console.WriteLine($"Cześć, {name}!");
}

private static void ShowGoodbye(string name)
{
    Console.WriteLine($"Do widzenia, {name}!");
}

Dwie metody o identycznym kształcie: przyjmują string, nic nie zwracają. W poprzednim wpisie o delegatach delegat był rozkazem bez treści – „skocz”, „strzelaj”. Nie dało się mu nic przekazać.

Teraz dokładamy parametr. I przy okazji odpowiemy na pytanie, które przy pierwszym kontakcie z delegatami zadaje sobie każdy: co właściwie musi się zgadzać, żeby metoda „pasowała” do delegata?

Złota Zasada 3 Kroków

Praca z delegatem wygląda tak samo w konsolowej zabawce i w silniku reguł biznesowych. Zawsze trzy kroki, zawsze w tej kolejności.

Wypróbuj

Złota Zasada 3 Kroków — krok po kroku

public delegate void DisplayMessage(string text);DisplayMessage myDelegate = ShowHello;myDelegate(“Jan”);myDelegate = ShowGoodbye;myDelegate(“Jan”);
Krok 1 · Definiujesz

Powstaje typ. Kontraktem jest string, nie nazwa text.

Krok 2 · Przypisujesz

Bez nawiasów. Kompilator sprawdza tu całą sygnaturę.

Krok 3 · Wywołujesz

Z nawiasami i z argumentem. Dopiero teraz coś się dzieje.

Konsola — naciśnij „Następny krok”…

Zwróć uwagę: linijka myDelegate("Jan"); w krokach 3 i 5 jest identyczna — ten sam argument, ten sam zapis. Zmieniło się wyłącznie to, co siedzi w zmiennej.

using System;

// KROK 1: DEFINIUJESZ — szablon metody: przyjmuje string, zwraca void.
public delegate void DisplayMessage(string text);

public class Program
{
    public static void Main()
    {
        // KROK 2: PRZYPISUJESZ — bez nawiasów.
        DisplayMessage myDelegate = ShowHello;

        // KROK 3: WYWOŁUJESZ — z nawiasami i z argumentem.
        myDelegate("Jan");

        // Ta sama zmienna, inna metoda, niezmieniona linijka wywołania.
        myDelegate = ShowGoodbye;
        myDelegate("Jan");
    }

    private static void ShowHello(string name)
    {
        Console.WriteLine($"Cześć, {name}!");
    }

    private static void ShowGoodbye(string name)
    {
        Console.WriteLine($"Do widzenia, {name}!");
    }
}

Definicję czytamy od prawej do lewej(string text) - przyjmuje jeden tekst; void - nic nie zwraca; DisplayMessage - tak nazywa się nowy typ; delegate - a całość to definicja typu, nie metody.

Argument "Jan" wchodzi do zmiennej myDelegate, ta przekazuje go do metody, na którą aktualnie wskazuje, i tam ląduje w parametrze name. Zmienna delegata jest przewodem - nie interesuje jej, co metoda zrobi z argumentem.

Zasada: 
gubisz się przy jakimkolwiek delegacie?
Zapytaj: gdzie jest definicja, gdzie przypisanie, gdzie wywołanie.
W dziewięciu przypadkach na dziesięć któryś z kroków jest po prostu w innym pliku.

Kontraktem jest typ, nie nazwa parametru

Tu jest sedno tego wpisu i punkt, którego większość kursów nie tłumaczy.

W definicji delegata parametr nazywa się text. W metodzie ShowHello nazywa się name. Zupełnie inne słowo. I to jest w pełni poprawne.

public delegate void DisplayMessage(string text);

private static void ShowHello(string name)       { }   // ✅ pasuje
private static void ShowHello2(string wiadomosc) { }   // ✅ też pasuje
private static void ShowHello3(string xyz)       { }   // ✅ i to pasuje

Kompilator porównuje wyłącznie typy. Nazwa parametru w definicji delegata to dokumentacja dla człowieka, nic więcej.

Dlatego na code review zobaczysz delegaty, w których nazwy parametrów nie mają nic wspólnego z podpiętymi metodami. To nie jest błąd ani niechlujstwo.

Zasada: 
nazwa metody nie liczy się wcale,
nazwa parametru liczy się tylko w jednym miejscu, patrz następna sekcja.

Pułapka #1: argument nazwany bierze nazwę z delegata

Jedyne miejsce, w którym nazwa parametru z definicji delegata realnie coś zmienia:

myDelegate(text: "Jan");     // ✅ 'text' — z definicji delegata
myDelegate(name: "Jan");     // ❌ CS1739
// The best overload does not have a parameter named 'name'

Wygląda to jak niekonsekwencja, ale jest logiczne:
w momencie kompilacji nikt nie wie, która metoda wyląduje w zmiennej w czasie działania programu. Kompilator ma do dyspozycji wyłącznie deklarację delegata, więc tylko stamtąd może wziąć nazwę.

Praktyczny wniosek:
nazywaj parametry delegatów sensownie. Trafiają do IntelliSense każdego, kto ten delegat wywoła, nawet jeśli podpięta metoda nazywa je zupełnie inaczej.

Zasada: 
text w delegate void DisplayMessage(string text) to nie ozdobnik, to publiczne API Twojego delegata.

Pułapka #2: delegat jest tak samo rygorystyczny jak metoda

myDelegate();                   // ❌ CS7036 — brak wymaganego argumentu
myDelegate("Jan", "Kowalski");  // ❌ CS1593 — delegat nie przyjmuje 2 argumentów
myDelegate(42);   // ❌ CS1503 — cannot convert from 'int' to 'string'

Żadnej niespodzianki i właśnie dlatego warto to zobaczyć. Delegat nie jest luźniejszy od zwykłego wywołania metody. To normalne wywołanie, tylko adres metody siedzi w zmiennej. Jeśli traktowałeś dotąd delegaty jako coś magicznego, ta sekcja powinna to odczarować.

Pułapka #3: sygnatura to komplet

Dopasowanie działa po całej sygnaturze: liczbie parametrów, ich typach i typie zwracanym. Zgodność jednego elementu nie wystarcza.

private static void ShowAge(int age) { }           // zły typ parametru
private static void Jump() { }                     // brak parametru
private static string AskName() { return "Jan"; }  // zły typ zwracany
DisplayMessage a = ShowAge;
// ❌ CS0123: No overload for 'ShowAge' matches delegate 'DisplayMessage'

DisplayMessage b = Jump;
// ❌ CS0123 — zero parametrów, a delegat wymaga jednego

DisplayMessage c = AskName;
// ❌ CS0407: 'string AskName()' has the wrong return type

Zwróć uwagę na Jump, metodę z poprzedniego wpisu. Pasowała do ButtonAction, ale do DisplayMessage już nie, bo liczba parametrów też jest częścią kontraktu.

To realna przewaga delegatów nad refleksją czy dynamic: niedopasowanie wychodzi w edytorze, nie u użytkownika.

Zasada: 
czytaj numer błędu.
CS0123 to niezgodne parametry,
CS0407 to niezgodny typ zwracany,
CS1503 to zły typ argumentu przy wywołaniu.

Ściągawka na code review

ZapisWynikDlaczego
DisplayMessage d = ShowHello;void (string) — kształt się zgadza
d("Jan");dokładnie jeden string
d();❌ CS7036brak wymaganego argumentu text
d("Jan", "Kowalski");❌ CS1593delegat nie przyjmuje dwóch argumentów
d(42);❌ CS1503int to nie string
d(name: "Jan");❌ CS1739nazwa pochodzi z delegata, nie z metody
DisplayMessage d = ShowAge;❌ CS0123int zamiast string
DisplayMessage d = AskName;❌ CS0407zwraca string zamiast void
Sprawdź się

Czy ta metoda pasuje do delegata?

Sześć kandydatur, jedna zagadka na trzy sekundy. Kliknij — jeśli nie pasuje, zobaczysz dokładnie ten komunikat, który pokaże Ci kompilator.

Szablon — public delegate void DisplayMessage(string text);
void ( string )
Wybierz metodę powyżej…
Sprawdzone: 0 z 6

Po co to, skoro mogę wywołać metodę wprost?

Uczciwe pytanie.
W powyższym przykładzie faktycznie nic nie zyskujesz, wszystko dzieje się w jednej metodzie Main.

Wartość pojawia się wtedy, gdy kod decydujący i kod wykonujący przestają siedzieć w tym samym miejscu. Kiedy definicja jest w jednym pliku, przypisanie w drugim, a wywołanie w trzecim, delegat staje się jedynym sensownym sposobem, żeby te trzy pliki nie musiały o sobie wiedzieć.

Na tej samej zasadzie działa każda lambda w LINQ i każdy handler zdarzenia - tam też ktoś inny decyduje, co ma się wykonać, a ktoś inny to wykonuje. W nowoczesnym C# rzadko piszesz własny delegate, bo wystarczają gotowe Func i Action, ale mechanizm pod spodem jest dokładnie ten sam.

Podsumowanie

  • Złota Zasada 3 Kroków: 
    definiujesz → przypisujesz (bez nawiasów) → wywołujesz (z nawiasami).
  • Kontraktem jest typ parametru, nie jego nazwa. 
    text i name pasują do siebie bez problemu.
  • Wyjątek: 
    argument nazwany bierze nazwę z definicji delegata, bo kompilator nie wie, która metoda tam wyląduje.
  • Sygnatura to komplet:
    liczba parametrów, ich typy i typ zwracany. Nazwa metody nie liczy się wcale.
  • Delegat jest tak samo rygorystyczny jak zwykłe wywołanie metody:
    CS7036, CS1593 i CS1503 to te same błędy, które dostałbyś bez delegata.


📘 Darmowa roadmapa Junior .NET Developer: dev-hobby.pl/lista-vip

Zadanie na 5 minut: 
zdefiniuj delegate void ShowPrice(decimal amount);, napisz ShowNetPrice i ShowGrossPrice (+23% VAT),
podepnij obie pod tę samą zmienną i wywołaj dla 100m.
Potem spróbuj podpiąć 
ShowPriceWithCurrency(decimal amount, string currency) 
— który numer błędu dostaniesz i która część sygnatury się nie zgadza?
Napisz w komentarzu.

Zobacz

📦 Kod źródłowy tego artykułu: zobacz na GitHub
👨‍💻
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.

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ę →