Delegat jako parametr w C# – pułapka bez komunikatu

public void Perform(SongDelegate song)
{
Console.WriteLine("Orkiestra gotowa, zaczynamy...");
song();
Console.WriteLine("Koniec utworu. Brawo!\n");
}
public void PerformSilently(SongDelegate song)
{
Console.WriteLine("Orkiestra gotowa, zaczynamy...");
Console.WriteLine("Koniec utworu. Brawo!\n");
}Dwie metody. Identyczna sygnatura – (SongDelegate) → void. Obie kompilują się bez ostrzeżenia. Wywołaj pierwszą, a usłyszysz utwór. Wywołaj drugą, a usłyszysz ciszę między dwoma tymi samymi napisami na konsoli.
Kompilator nie zgłosi ani jednego błędu w żadnym z tych dwóch przypadków. Sprawdza wyłącznie to, czy parametr song ma poprawny typ, nigdy to, czy ciało metody faktycznie go wywołuje.
To jest sedno delegata jako parametru metody: mechanika przekazania jest identyczna jak przy int czy string, ale przyjęcie delegata w liście parametrów niczego nie gwarantuje.
Delegat, który podróżuje
Do tej pory w tej serii delegat mieszkał w jednej metodzie, definiowałeś go, przypisywałeś i wywoływałeś, wszystko w Main(). Tym razem delegat pierwszy raz podróżuje jako argument do innej metody.
using System;
namespace BeginnerDelegateParameterExample;
// 1. Definicja delegata, reprezentuje utwór bez parametrów i bez wyniku
public delegate void SongDelegate();
// 2. Orkiestra, jej metoda PRZYJMUJE delegat jako zwykły parametr
public class Orchestra
{
public void Perform(SongDelegate song)
{
Console.WriteLine("Orkiestra gotowa, zaczynamy...");
song();
Console.WriteLine("Koniec utworu. Brawo!\n");
}
}
public class Program
{
public static void Main()
{
var orchestra = new Orchestra();
// KROK 1: Przekazujemy metodę PlayJazz jako argument
orchestra.Perform(PlayJazz);
// KROK 2: Ta sama metoda Perform, INNY utwór jako argument
orchestra.Perform(PlayRock);
// BONUS: Przekazujemy lambdę “w locie”, bez osobnej metody
orchestra.Perform(() => Console.WriteLine("...gra Twoją własną melodię!"));
}
private static void PlayJazz()
{
Console.WriteLine("Gramy improwizowanego jazza...");
}
private static void PlayRock()
{
Console.WriteLine("Gramy energetycznego rocka...");
}
}Popatrz na listę parametrów Perform(SongDelegate song) – typ parametru to nazwa delegata, dokładnie tak samo, jak gdyby to był int volume albo string title. Perform nie wie, CZYM jest song. Wie tylko, że dostanie coś, co da się wywołać bez parametrów i bez wyniku i że ma to jawnie odpalić w linijce song().
Zasada:
parametr metody może mieć typ delegata. Przekazujesz nazwę metody bez nawiasów, dokładnie jak przy przypisaniu do zmiennej, różni się tylko miejsce składniowe: argument wywołania, nie prawa strona przypisania.
Ten sam parametr, dwa różne utwory jako argument
Powstaje typ. Kontraktem jest () → void — nic nie wchodzi, nic nie wraca.
Perform(SongDelegate song) — typ parametru to nazwa delegata, jak int czy string.
Bez nawiasów, jak zawsze — ale tym razem to argument wywołania, nie przypisanie.
Zwróć uwagę: linijka 2 (definicja Perform) nie zmienia się między
krokami 3 i 4 — zmienia się tylko to, KTÓRA metoda wpływa jako argument. Dokładnie ta sama
Złota Zasada co przy zwykłym przypisaniu, tylko w nowym miejscu składniowym.
Krok 2 i 3 — parametr i argument
W poprzednich wpisach tej serii delegat zawsze lądował w zmiennej lokalnej. Tutaj ląduje w parametrze metody:
// KROK 2 — metoda PRZYJMUJE delegat
public void Perform(SongDelegate song)
{
// wywołanie w środku ciała metody
song();
}
// KROK 3 — PRZEKAZUJESZ jako argument
orchestra.Perform(PlayJazz);Nazwa metody bez nawiasów wpływa jako argument, ląduje w parametrze song - a dopiero WEWNĄTRZ ciała Perform, w linijce song(), faktycznie się wykonuje. To rozdzielenie „przekazania” od „wywołania” jest źródłem obu pułapek poniżej.
Pułapka #1: delegat-parametr, którego nikt nie wywołuje
public void PerformSilently(SongDelegate song)
{
Console.WriteLine("Orkiestra gotowa, zaczynamy...");
// song(); ← ta linijka zniknęła
Console.WriteLine("Koniec utworu. Brawo!\n");
}
PerformSilently ma dokładnie taką samą sygnaturę jak Perform. Parametr song jest poprawnym SongDelegate - kompilator jest w pełni usatysfakcjonowany. A mimo to na konsoli zobaczysz „gotowa, zaczynamy”, potem ciszę, potem „koniec utworu”. Utwór nigdy nie zagrał, bo nikt w środku metody nie napisał song().
To jest lustrzane odbicie pułapki, którą znasz z delegatu zwracającego wartość: tam wynik ginął, bo nikt go nie ODBIERAŁ z delegata. Tutaj dźwięk ginie, bo nikt nie ODPALA delegata, który już czeka w środku metody, gotowy do użycia.
Zasada:
sam fakt, że metoda PRZYJMUJE delegat jako parametr, niczego nie gwarantuje. Musisz go jawnie wywołać w ciele metody, tak jak każdą inną zmienną typu delegata.
Gdzie ginie song()?
Zwróć uwagę: obie karty pokażą zielony badge „kompiluje się”.
Kompilator sprawdza wyłącznie typ parametru song — a jednak tylko jedna metoda
faktycznie odpala przekazany delegat. Ta pułapka nie zostawia po sobie żadnego śladu
w edytorze ani żadnego komunikatu w oknie Błędy.
Pułapka #2: liczba parametrów to wciąż część sygnatury
private static void PlaySongLoudly(int volume) => Console.WriteLine($"Gramy jazza na {volume}%...");
orchestra.Perform(PlaySongLoudly);
// CS1503: Argument 1: cannot convert from 'method group' to 'SongDelegate'Jeden parametr zamiast zera. Ta sama zasada, którą znasz z wcześniejszych wpisów tej serii - różnica w tym, że konwersja metoda-grupa-na-delegat dzieje się tym razem w miejscu ARGUMENTU wywołania, nie przy przypisaniu do zmiennej. Kod błędu i komunikat są identyczne - to ten sam mechanizm sprawdzania sygnatury, uruchomiony w nowym miejscu składniowym.
Pułapka #3: typ zwracany nadal się liczy
private static bool TryPlayJazz()
{
Console.WriteLine("Gramy improwizowanego jazza...");
return true;
}
orchestra.Perform(TryPlayJazz);
// CS0407: 'bool Pulapki.TryPlayJazz()' has the wrong return typeTryPlayJazz zwraca bool, SongDelegate deklaruje void. Dokładnie ta sama różnica, którą widziałeś przy PrintGreeting i PrintSum we wcześniejszych wpisach — tylko odwrócona: tam delegat OCZEKIWAŁ wartości, a metoda jej nie dawała. Tutaj delegat NIE oczekuje niczego, a metoda i tak coś zwraca. Kompilator jest równie surowy w obie strony.
Ściągawka na code review
| Zapis | Wynik | Dlaczego |
|---|---|---|
orchestra.Perform(PlayJazz); | ✅ gra | () → void — kształt się zgadza, song() jest wywołane |
orchestra.PerformSilently(PlayJazz); | ⚠️ ✅ kompiluje się | sygnatura pasuje — ale song() nigdy nie pada w ciele metody |
orchestra.Perform(PlaySongLoudly); | ❌ CS1503 | jeden parametr zamiast zera |
orchestra.Perform(TryPlayJazz); | ❌ CS0407 | zwraca bool zamiast void |
Fundament pod Callback, Strategy i LINQ
Ten jeden mechanizm - delegat jako zwykły parametr metody - to fundament pod większość wzorców, które poznasz w dalszej części tej serii. Callback raportujący postęp, wzorzec Strategy podmieniający algorytm w locie, nawet to, jak pod maską działa .Where() z LINQ - wszystkie opierają się na dokładnie tej samej idei: metoda przyjmuje gotowe zachowanie jako argument, zamiast znać je na sztywno w swoim ciele.
Zero dodatkowej złożoności w samym dzisiejszym przykładzie - żadnych dodatkowych parametrów, żadnej wartości zwracanej, żadnej logiki biznesowej. Cała uwaga poszła na jeden fakt: delegat może być parametrem.
Podsumowanie
- Parametr metody może mieć typ delegata - dokładnie tak jak
intczystring. Przekazujesz nazwę metody bez nawiasów, tak jak przy przypisaniu. - To metoda odbierająca decyduje, KIEDY wywołać delegat - i musi to zrobić jawnie. Przyjęcie delegata jako parametru niczego nie gwarantuje.
- Pułapka bez komunikatu: metoda o identycznej sygnaturze, która nie wywołuje przyjętego delegata, kompiluje się bez ostrzeżenia i po prostu nic nie robi.
- Sygnatura obowiązuje także w miejscu argumentu - tylko numer błędu jest inny niż przy przypisaniu: zła liczba parametrów to
CS1503(CS0123zobaczysz, gdy przypisujesz metodę do zmiennej delegata), a zły typ zwracany to niezmiennieCS0407. - To fundament pod Callback, Strategy i
.Where()z LINQ - wszystkie opierają się na delegacie przekazanym jako parametr.
🎥 Wersja wideo: ten sam przykład krok po kroku — Delegaty w C# #6 — Delegat jako parametr metody
📂 Kod źródłowy: github.com/dev-hobby/CSharpDelegates — projekt DevHobby.L06-OrchestraConductor
📘 Darmowa roadmapa Junior .NET Developer: dev-hobby.pl/lista-vip
Zadanie na 5 minut:
dopisz do Orchestra
metodę PerformWithSupport(SongDelegate support, SongDelegate mainAct), która najpierw wywołuje support, potem mainAct.
Wywołaj ją dla co najmniej dwóch różnych par, w tym jednej z lambdą jako support. Napisz w komentarzu, co decyduje o kolejności grania - kolejność deklaracji metod w Main() czy kolejność wywołań w ciele PerformWithSupport.
Zobacz
- Delegat zwracający bool w C# - pułapka bez ostrzeżenia kompilatora, cz. 3
- Delegat z wieloma parametrami w C# - pułapka bez ostrzeżenia kompilatora, cz. 2
- Delegat zwracający wartość w C# - pułapka bez ostrzeżenia kompilatora, cz. 1
- LINQ w C# — kompletny przewodnik -
.Where()i predykat przekazany jako parametr w akcji
📦 Kod źródłowy tego artykułu: zobacz na GitHub
🚀 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.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
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ę →