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

Delegat zwracający wartość w C# – pułapka bez ostrzeżenia

Delegat zwracający wartość w C# — provider() i wynik, który wraca do zmiennej
private static string GetMorningGreeting()
{
    return "Dzień dobry! Gotowy na nowy dzień?";
}

private static string GetEveningGreeting()
{
    return "Dobry wieczór! Czas odpocząć od kodu.";
}

Console.WriteLine($"[7:00  - UI]: {GetMorningGreeting()}");
Console.WriteLine($"[20:00 - UI]: {GetEveningGreeting()}");

Nagłówek aplikacji ma pokazywać inne powitanie rano, inne wieczorem. Ten kod działa ale jest źle zaprojektowany. Komponent nagłówka musi znać wszystkie źródła tekstu. Dodanie powitania świątecznego to edycja w miejscu, które w ogóle nie powinno o takich rzeczach wiedzieć.

dwóch poprzednich wpisach delegat był rozkazem, wydawałeś polecenie, nic nie wracało. Tym razem delegat staje się pytaniem, a odpowiedź po raz pierwszy wraca do Twojego kodu.

Złota Zasada 3 Kroków, tym razem z wynikiem

Wypróbuj

Złota Zasada 3 Kroków — teraz z wynikiem

public delegate string GreetingProvider();GreetingProvider provider = GetMorningGreeting;string message = provider();provider = GetEveningGreeting;message = provider();
Krok 1 · Definiujesz

Powstaje typ. Kontraktem jest string () — nic nie przyjmuje, coś zwraca.

Krok 2 · Przypisujesz

Bez nawiasów, jak zawsze. Jeszcze nic się nie wykonało.

Krok 3 · Wywołujesz i ODBIERASZ

Z nawiasami — i po raz pierwszy wynik wraca do zmiennej.

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

Zwróć uwagę: linijka message = provider(); w krokach 3 i 5 wygląda tak samo, ale za każdym razem inna metoda stoi pod zmienną — inny tekst wraca.

Te same trzy kroki co zawsze: definiujesz, przypisujesz, wywołujesz, tylko krok trzeci ma teraz dodatkowy element: coś odbierasz.

using System;

namespace BeginnerDelegateReturnValue;

public delegate string GreetingProvider();

public class Program
{
    public static void Main()
    {
        Console.WriteLine("=== ASYSTENT NAGŁÓWKA APLIKACJI ===\n");

        GreetingProvider provider = GetMorningGreeting;

        string message = provider();

        Console.WriteLine($"[7:00  - UI]: {message}");

        provider = GetEveningGreeting;

        message = provider();

        Console.WriteLine($"[20:00 - UI]: {message}");

        provider = () => "Sto lat! Masz dziś urodziny!";

        Console.WriteLine($"[00:01 - UI]: {provider()}");
    }

    private static string GetMorningGreeting()
    {
        return "Dzień dobry! Gotowy na nowy dzień?";
    }

    private static string GetEveningGreeting()
    {
        return "Dobry wieczór! Czas odpocząć od kodu.";
    }
}

Definicję czytamy jak zawsze od prawej do lewej
() - nie przyjmuje żadnych argumentów;
string - zwraca tekst; 
GreetingProvider - tak nazywa się nowy typ.
Zwróć uwagę na nazwę: nie GreetingAction, tylko Provider dostawca. W .NET nazwa delegata zwykle mówi, jaką rolę pełni: 
Action wykonuje,
Provider albo Factory dostarcza.

Trzeci krok zawiera lambdę () => "Sto lat!..." - skrótowy zapis funkcji bez osobnej metody. To nadal ten sam delegat i ten sam kontrakt string (), zmienił się tylko sposób zapisu. Pełne rozłożenie lambd to temat na osobny wpis.

Zasada: 
delegat zwracający wartość to nadal
DEFINIUJESZ → PRZYPISUJESZ → WYWOŁUJESZ.
Różnica jest wyłącznie w trzecim kroku - po prawej stronie stoi teraz = provider(), nie sam provider();.

Delegat jako pytanie, nie rozkaz

provider nie wie, która metoda jest pod niego akurat podpięta. Wie tylko, że po wywołaniu dostanie stringa - kontrakt gwarantuje typ wyniku, nie jego treść.

To zmiana kategorii narzędzia. Delegat z poprzednich dwóch wpisów wykonywał efekt uboczny, coś się działo na ekranie albo w grze. Ten delegat oddaje dane i to wywołujący decyduje, co z nimi zrobić: wypisać, zapisać do pliku, wysłać jako odpowiedź HTTP, sprawdzić w teście.

Pułapka #1: wynik, który znika bez żadnego ostrzeżenia

Obie linijki kompilują się

Gdzie ląduje wynik provider()?

Bez odbiorcy
GreetingProvider provider = GetMorningGreeting;provider();
konsola…
Z odbiorcą
GreetingProvider provider = GetMorningGreeting;string message = provider();Console.WriteLine(message);
konsola…

Zwróć uwagę: obie karty pokażą zielony badge „✅ kompiluje się”. Kompilator nie ma zdania na temat tego, co robisz z wynikiem — a jednak tylko jedna linijka faktycznie coś pokazuje na ekranie. Ta pułapka nie zostawia po sobie żadnego śladu w edytorze.

To najgroźniejsza pułapka w całej serii, bo kompilator milczy.

GreetingProvider provider = GetMorningGreeting;

provider();          // ✅ kompiluje się, ale tekst przepada

Delegat zwracający wartość można wywołać jak zwykłą instrukcję, bez odbierania wyniku. Metoda wykona się poprawnie, zwróci tekst i ten tekst poleci do kosza. Zero błędu, zero ostrzeżenia, zero śladu w logach kompilatora.

Zauważ symetrię z pierwszym wpisem tej serii:
tam problemem były nawiasy, których nie powinno być. Tutaj problemem jest brak odbiorcy wyniku. Pierwsze kompilator wyłapie. Drugiego nigdy.

string message = provider();   // ✅ wynik trafia do zmiennej
Console.WriteLine(message);

Zasada: 
przy każdym delegacie zwracającym wartość zadaj sobie pytanie:
gdzie ląduje wynik?
Jeśli nie potrafisz wskazać zmiennej, prawdopodobnie właśnie go zgubiłeś.

Pułapka #2: typ zmiennej musi zgadzać się z typem zwracanym

int number = provider();
// ❌ CS0029: Cannot implicitly convert type 'string' to 'int'

Nic zaskakującego, ale warto to zestawić z pierwszym wpisem tej serii, bo numer błędu jest ten sam, treść inna. Tam CS0029 mówił „cannot convert void", tu mówi „cannot convert string". Ta sama rodzina błędu, inna przyczyna, komunikat trzeba czytać, nie tylko rozpoznawać po numerze.

Pułapka #3: „wypisuje" to nie to samo co „zwraca"

private static void PrintGreeting()
{
    Console.WriteLine("Dzień dobry! Gotowy na nowy dzień?");
}

GreetingProvider bad = PrintGreeting;
// ❌ CS0407: 'void Pulapki.PrintGreeting()' has the wrong return type

PrintGreeting pokazuje powitanie, sama decyduje, że trafi do konsoli. Delegat GreetingProvider oczekuje metody, która tekst oddaje i pozwala decydować wywołującemu, co z nim zrobić. Z punktu widzenia użytkownika efekt na ekranie wygląda identycznie. Z punktu widzenia architektury to dwie różne rzeczy i dlatego typ zwracany jest częścią kontraktu, a nie szczegółem implementacji.

To też powód, dla którego delegaty zwracające wartość są łatwiejsze w testowaniu: sprawdzasz, co metoda zwróciła, zamiast przechwytywać konsolę.

Ten sam mechanizm dopasowania działa też w drugą stronę, brak parametru albo zły typ parametru odrzuca kandydata równie bezwzględnie:

private static string GetGreetingFor(string name)
{
    return $"Dzień dobry, {name}!";
}

GreetingProvider bad2 = GetGreetingFor;
// ❌ CS0123: No overload for 'GetGreetingFor' matches delegate 'GreetingProvider'
// (przyjmuje parametr, a GreetingProvider nie deklaruje żadnego)

Zasada: 
typ zwracany to pełnoprawna część sygnatury delegata, tak samo jak liczba i typy parametrów. Metoda musi pasować całością, nie jednym elementem.

Ściągawka na code review

ZapisWynikDlaczego
GreetingProvider p = GetMorningGreeting;string () - kształt się zgadza
string m = p();wynik trafia do zmiennej
p();⚠️ kompiluje sięwynik zwrócony i natychmiast zgubiony
int n = p();❌ CS0029delegat zwraca string, nie int
GreetingProvider p = PrintGreeting;❌ CS0407PrintGreeting zwraca 
void, nie string
GreetingProvider p = GetGreetingFor;❌ CS0123metoda wymaga parametru, delegat żadnego nie deklaruje

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

Uczciwe pytanie, tak samo jak przy poprzednich dwóch wpisach. W przykładzie z nagłówkiem faktycznie nic nie zyskujesz, dopóki wszystko siedzi w jednej metodzie Main.

Wartość pojawia się, gdy źródło danych i kod, który ich potrzebuje, przestają być tym samym miejscem w kodzie. Nagłówek pyta „daj mi tekst" i nie musi wiedzieć, czy odpowiedź przyjdzie z bazy danych, z pliku konfiguracyjnego czy z prostego return.

To dokładnie ten sam mechanizm, na którym stoi każda metoda LINQ: .Where().Select().OrderBy() 
wywołują Twój delegat i oczekują wyniku z powrotem, żeby zdecydować, co zrobić z kolejnym elementem kolekcji. W nowoczesnym C# rzadko piszesz własny delegate 
zwracający wartość, bo wystarcza gotowy Func<string> - ale mechanizm pod spodem jest identyczny z tym, co właśnie napisałeś.

Podsumowanie

  • Delegat może zwracać wartość - wtedy przestaje być rozkazem, a staje się dostawcą danych.
  • Wywołanie wygląda tak samo jak wcześniej, ale wynik trzeba mieć gdzie przyjąć. Zignorowanie go kompiluje się bez ostrzeżenia, to najgroźniejsza pułapka tego wpisu.
  • Typ zwracany jest częścią kontraktu, tak samo jak liczba i typy parametrów z poprzedniego wpisu. Metoda, która tekst wypisuje, nie pasuje do delegata, który tekst zwraca.
  • Ten sam numer błędu może znaczyć co innego. CS0029 w tym wpisie i w pierwszym wpisie serii ma inną treść, liczy się komunikat, nie sam numer.
  • Delegaty zwracające wartość łatwiej testować - sprawdzasz zwrócony wynik zamiast przechwytywać efekty uboczne.

📂 Kod źródłowy: github.com - projekt DevHobby.L03-GreetingProvider
📘 Darmowa roadmapa Junior .NET Developer: dev-hobby.pl/lista-vip

Zadanie na 5 minut: 
zdefiniuj delegate string MessageProvider();,
napisz trzy metody — poranną, roboczą i wieczorną - zwracające tekst przez return. Podmieniaj przypisanie i za każdym razem zapisz wynik do zmiennej string przed wypisaniem.
Na końcu napisz obok siebie provider = GetMorning; oraz provider = GetMorning(); - jaki komunikat dostajesz dla drugiej linijki i czym różni się od CS0029 z pierwszego wpisu tej serii?

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