🔥 Darmowa Roadmapa .NET — kompletny plan nauki do pierwszej pracy. Pobierz i dołącz do Listy VIP →

Delegat zwracający bool w C# – pułapka o jeden znak

Delegat zwracający bool w C# — reguła tak/nie wstawiona wprost do if
private static bool IsAdult(int age) => age >= 18;
private static bool IsAdultWrong(int age) => age > 18;

Dwie metody. Identyczna sygnatura – (int) → bool. Jedna sprawdza „18 i więcej”, dokładnie tak, jak sugeruje jej nazwa. Druga sprawdza „więcej niż 18” – jeden znak różnicy w kodzie źródłowym.

Podepnij obie pod ten sam delegat i wywołaj rule(18). Pierwsza zwróci true. Druga zwróci falseKompilator nie zgłosi ani jednego ostrzeżenia w żadnym z tych dwóch przypadków.

To jest sedno delegatów zwracających bool: mechanika przypisania i wywołania jest identyczna jak przy każdym innym typie zwracanym, ale kompilator sprawdza wyłącznie sygnaturę, nigdy operator porównania w środku metody.

Delegat, który podejmuje decyzję

Do tej pory delegat w tej serii albo coś robił (void), albo coś zwracał (stringint). Delegat zwracający bool robi coś nowego: odpowiada na pytanie.

using System;
namespace BeginnerDelegateAgeGateExample;

// 1. Definicja delegata – przyjmuje int, zwraca bool (decyzję)
public delegate bool AgeRule(int age);

public class Program
{
    public static void Main()
    {
        int[] goscie = { 15, 20, 40, 70 };

        Console.WriteLine("=== STREFA DLA DOROSŁYCH (18+) ===");
        // KROK 1: Podpinamy regułę dla dorosłych
        AgeRule rule = IsAdult;
        CheckAccess(goscie, rule);

        Console.WriteLine("\n=== STREFA SENIORA (65+) ===");
        // KROK 2: Pod TĘ SAMĄ zmienną podpinamy inną regułę
        rule = IsSenior;
        CheckAccess(goscie, rule);

        Console.WriteLine("\n=== STREFA VIP (własna reguła “w locie”) ===");
        // BONUS: Reguła zdefiniowana lambdą, bez osobnej metody
        rule = wiek => wiek >= 21 && wiek <= 60;
        SprawdzGosci(goscie, rule);
    }

    // Metoda pomocnicza – nie wie NIC o konkretnej regule,
    // po prostu woła przekazany delegat dla każdego gościa
    private static void CheckAccess(int[] goscie, AgeRule rule)
    {
        foreach (int wiek in goscie)
        {
            // 🔥 Wywołujemy delegat – on decyduje, nie ta metoda!
            bool wpuszczony = rule(wiek);
            string status = wpuszczony ? "✅ WPUSZCZONY" : "⛔ ODRZUCONY";
            Console.WriteLine($"Wiek {wiek}: {status}");
        }
    }

    // Metoda #1 pasująca do delegata AgeRule (int -> bool)
    private static bool IsAdult(int age) => age >= 18;

    // Metoda #2 pasująca do delegata AgeRule (int -> bool)
    private static bool IsSenior(int age) => age >= 65;
}

Definicję czytamy jak zawsze od prawej do lewej(int age) – przyjmuje jedną liczbę całkowitą; bool – zwraca decyzję tak/nie; AgeRule – tak nazywa się nowy typ. Metoda CheckAccess pilnuje dwóch zupełnie różnych stref, nie zmieniając ani jednej własnej linijki, zmienia się tylko reguła podpięta pod rule.

Zasada: 
delegat zwracający bool wstawia się dokładnie tam, gdzie zwykły warunek logiczny w if, w &&, w operatorze ?:
rule(wiek) zachowuje się jak każdy inny wyrażenie logiczne w Twoim kodzie.

Wywołanie jako warunek

Wypróbuj

Ten sam wiek, dwie różne reguły — krok po kroku

public delegate bool AgeRule(int age);AgeRule rule = IsAdult;if (rule(20)) Console.WriteLine("Wejście dozwolone.");rule = IsSenior;if (rule(20)) Console.WriteLine("Wejście dozwolone.");
Krok 1 · Definiujesz

Powstaje typ. Kontraktem jest (int) → bool — wiek wchodzi, decyzja wychodzi.

Krok 2 · Przypisujesz

Bez nawiasów. rule to na razie tylko nazwa metody, nic się jeszcze nie wykonało.

Krok 3 · Wywołujesz jako warunek

rule(20) wstawia się wprost do if — dokładnie jak zwykły bool.

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

Zwróć uwagę: ostatni krok nic nie wypisuje — wiek 20 nie kwalifikuje się jako senior (65+), więc warunek if jest fałszywy i cała linijka Console.WriteLine po prostu się nie wykonuje. To normalne zachowanie zwykłego if, tylko że reguła stojąca za nim jest teraz podmienialna.

poprzednim wpisie o delegacie z wieloma parametrami wynik wywołania trafiał do zmiennej i był wypisywany. Tutaj wynik steruje przepływem programu:

if (rule(20))
{
    Console.WriteLine("Wejście dozwolone.");
}

Nie ma tu nic magicznego – rule(20) zwraca bool, więc if traktuje je jak każdy inny warunek. Różnica względem zwykłej metody bool polega wyłącznie na tym, że konkretna reguła stojąca za rule może się zmienić w trakcie działania programu, bez zmiany kodu, który ją wywołuje.

Pułapka #1: zła granica warunku daje zły wynik bez błędu

private static bool IsAdultWrong(int age) => age > 18;
AgeRule rule = IsAdultWrong;
bool wynik = rule(18);

IsAdultWrong przyjmuje int i zwraca bool – sygnatura pasuje idealnie do AgeRule. Kompilator nie ma żadnego powodu, żeby zaprotestować.

Różnica jest w jednym znaku: > zamiast >=. Dla wieku dokładnie 18 ta metoda zwraca false – osoba, która właśnie skończyła 18 lat, zostaje odrzucona, mimo że reguła w założeniu miała brzmieć „18 i więcej”.

To jest trzecia odsłona tej samej rodziny błędów, którą znasz z poprzednich wpisów tej serii: w delegacie zwracającym wartość ginął cały wynik, w delegacie z wieloma parametrami zamieniały się nazwy parametrów, tutaj myli się granica warunku. Za każdym razem kompilator widzi wyłącznie sygnaturę – int na wejściu, bool na wyjściu – i nigdy logikę schowaną w środku.

Zasada: 
Poprawna sygnatura nie oznacza poprawnej reguły.
Przy delegatach zwracających bool sprawdź granice porównań osobno
– kompilator tego za Ciebie nie zrobi.

Obie linijki kompilują się

Ta sama sygnatura, inna granica

Granica domknięta (>=)
private static bool IsAdult(int age) => age >= 18;rule = IsAdult;rule(18)
wynik…
Granica otwarta (>)
private static bool IsAdultWrong(int age) => age > 18;rule = IsAdultWrong;rule(18)
wynik…

Zwróć uwagę: obie karty pokażą zielony badge „✅ kompiluje się”IsAdultWrong ma dokładnie taką samą sygnaturę jak IsAdult. Kompilator nie ma jak wiedzieć, że w środku stoi > zamiast >=. Dla wieku dokładnie 18 jedna metoda mówi „tak”, druga „nie”.

Pułapka #2: liczba parametrów to wciąż część sygnatury

private static bool HasAccessLevel(int age, int minLevel) => age >= minLevel;

AgeRule bad = HasAccessLevel;
// ❌ CS0123: No overload for 'HasAccessLevel' matches delegate 'AgeRule'

Dwa parametry zamiast jednego. Ta sama zasada, którą znasz z wcześniejszych wpisów, tam różnica wynosiła zero kontra jeden albo trzy kontra dwa, tutaj dwa kontra jeden. Liczba parametrów zawsze musi się zgadzać co do joty, niezależnie od tego, co delegat zwraca.

Pułapka #3: typ zwracany nadal się liczy

private static void PrintIsAdult(int age) => Console.WriteLine(age >= 18);

AgeRule bad2 = PrintIsAdult;
// ❌ CS0407: 'void Pulapki.PrintIsAdult(int)' has the wrong return type

PrintIsAdult wypisuje decyzję, ale jej nie zwraca, dokładnie ta sama różnica, co przy PrintSum i PrintGreeting z wcześniejszych wpisów tej serii. Delegat zwracający bool nie akceptuje metody zwracającej void, nawet jeśli „w praktyce robi to samo”.

Ściągawka na code review

ZapisWynikDlaczego
AgeRule d = IsAdult;(int) → bool – kształt się zgadza
d(18) na IsAdult✅ truegranica domknięta (>=)
AgeRule d = IsAdultWrong;⚠️ ✅ kompiluje sięsygnatura pasuje – ale granica w metodzie jest inna
d(18) na IsAdultWrong❌ falsezero błędu kompilacji, zły wynik logiczny
AgeRule d = HasAccessLevel;❌ CS0123dwa parametry zamiast jednego
AgeRule d = PrintIsAdult;❌ CS0407zwraca void zamiast bool

Po co to, skoro mogę po prostu napisać if?

Tak samo jak przy poprzednich wpisach w tym przykładzie faktycznie nic nie zyskujesz, dopóki cała logika siedzi w jednej metodzie Main.

Wartość pojawia się, gdy reguła jest wybierana dynamicznie, na przykład na podstawie konfiguracji albo typu użytkownika, tak jak w przykładzie z ochroniarzem: ten sam kod pilnujący wejścia obsługuje strefę dla dorosłych, strefę seniora i dowolną inną strefę, o ile dostanie odpowiednią regułę.

Ten kształt – jedno wejście, decyzja na wyjściu – to dokładnie to, co w nowoczesnym C# reprezentuje gotowy Predicate<int>: ten sam kontrakt (T) → bool, tylko bez własnej definicji delegate. To również fundament pod metodę .Where() w LINQ, która woła dokładnie taki predykat dla każdego elementu kolekcji.

Podsumowanie

  • Delegat zwracający bool wstawia się dokładnie tam, gdzie zwykły warunek logiczny – w if&&?:. Mechanika przypisania i wywołania się nie zmienia.
  • Poprawna sygnatura nie oznacza poprawnej reguły. Kompilator sprawdza typy, nigdy operator porównania w środku metody: > zamiast >= przechodzi bez ostrzeżenia.
  • To trzecia odsłona rodziny błędów bez komunikatu kompilatora w tej serii: ginący wynik, zamienione parametry, zła granica warunku.
  • CS0123 i CS0407 wracają w tej samej roli co przy delegatach void, string i int z wcześniejszych wpisów.
  • Predicate<int> to ten sam kontrakt co własny delegate bool AgeRule(int age), tylko bez własnej definicji typu i fundament pod .Where() z LINQ.

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

Zadanie na 5 minut: 

zdefiniuj delegate bool AccessRule(int age, bool hasPermission);,
napisz metody AllowAdults (tylko pełnoletni) i AllowWithPermission (pełnoletni lub ma pozwolenie), a potem przypisz obie kolejno do tej samej zmiennej i wywołaj dla minimum trzech zestawów danych. Napisz w komentarzu, dla którego zestawu danych obie metody dają różny wynik.

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