🔥 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 wieloma parametrami w C# – pułapka bez błędu

Delegat z wieloma parametrami w C# — dwa parametry i wartość zwracana naraz
private static int Subtract(int a, int b) => a - b;
private static int SubtractWrong(int b, int a) => a - b;

Dwie metody. Identyczna sygnatura – (int, int) → int. Jedna liczy a - b tak, jak sugeruje jej nazwa. Druga też liczy a - b, tylko że jej parametry nazywają się b i a, w tej kolejności.

Podepnij obie pod ten sam delegat i wywołaj operation(5, 3). Pierwsza da 2. Druga da -2Kompilator nie zgłosi ani jednego ostrzeżenia w żadnym z tych dwóch przypadków.

To jest sedno delegatów z wieloma parametrami: mechanika jest identyczna jak przy jednym, ale przy kilku parametrach tego samego typu pojawia się pułapka, o której poprzedni wpis tej serii nawet nie wspominał – bo przy jednym parametrze nie miała jak zaistnieć.

Dwa parametry, ta sama zasada

Wypróbuj

Dwa wejścia, jedno wyjście — krok po kroku

public delegate int MathOperation(int a, int b);MathOperation operation = Add;operation(liczbaA, liczbaB)operation = Subtract;operation(liczbaA, liczbaB)
Krok 1 · Definiujesz

Powstaje typ. Kontraktem jest (int, int) → int — dwa wejścia, jedno wyjście.

Krok 2 · Przypisujesz

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

Krok 3 · Wywołujesz

Z nawiasami, z dwoma argumentami, w tej samej kolejności co w definicji.

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

Zwróć uwagę: linijka operation(liczbaA, liczbaB) w krokach 3 i 5 jest identyczna — zmieniło się wyłącznie to, jaka metoda stoi pod zmienną operation.

Złota Zasada 3 Kroków działa dokładnie tak samo, niezależnie od tego, ile parametrów przyjmuje delegat.

using System;

namespace BeginnerDelegateCalculatorExample;

// KROK 1: Definiujemy delegat przyjmujący DWA parametry int i zwracający int.
public delegate int MathOperation(int a, int b);

public class Program
{
    public static void Main()
    {
        Console.WriteLine("=== PROSTY KALKULATOR ===\n");

        int liczbaA = 5;
        int liczbaB = 3;

        MathOperation operation = Add;
        int result = operation(liczbaA, liczbaB);
        Console.WriteLine($"{liczbaA} + {liczbaB} = {result}");

        operation = Multiply;
        result = operation(liczbaA, liczbaB);
        Console.WriteLine($"{liczbaA} * {liczbaB} = {result}");

        operation = Subtract;
        result = operation(liczbaA, liczbaB);
        Console.WriteLine($"{liczbaA} - {liczbaB} = {result}");

        operation = (a, b) => b == 0 ? 0 : a / b;
        result = operation(liczbaA, liczbaB);
        Console.WriteLine($"{liczbaA} / {liczbaB} = {result}");
    }

    private static int Subtract(int a, int b) => a - b;

    private static int Multiply(int a, int b) => a * b;

    private static int Add(int a, int b) => a + b;
}

Definicję czytamy jak zawsze od prawej do lewej(int a, int b) - przyjmuje dwie liczby całkowite; int - zwraca jedną; MathOperation - tak nazywa się nowy typ. Trzy operacje, jedna zmienna, zero if i zero switch w miejscu, gdzie operacja jest używana.

Zasada: 
liczba parametrów nie ma dla delegata żadnego specjalnego znaczenia. Kompilator sprawdza listę typów pozycyjnie, jeden parametr, dwa, dziesięć, zasada jest identyczna.

Kontraktem jest lista typów, nie ich nazwy

poprzednim wpisie ustaliliśmy, że nazwa parametru w delegacie to dokumentacja dla człowieka, nie kontrakt dla kompilatora. Przy jednym parametrze ta zasada była całkowicie nieszkodliwa, nie ma z czym pomylić text i name, skoro parametr jest jeden.

Przy dwóch parametrach tego samego typu ta sama własność języka przestaje być nieszkodliwa.

Pułapka #1:
zamienione nazwy parametrów dają zły wynik bez błędu

Obie linijki kompilują się

Ta sama sygnatura, inny wynik

Nazwy zgodne z sensem
private static int Subtract(int a, int b) => a - b;operation = Subtract;operation(5, 3)
wynik…
Nazwy zamienione
private static int SubtractWrong(int b, int a) => a - b;operation = SubtractWrong;operation(5, 3)
wynik…

Zwróć uwagę: obie karty pokażą zielony badge „✅ kompiluje się”SubtractWrong ma dokładnie taką samą sygnaturę jak Subtract. Kompilator nie ma jak wiedzieć, że parametry zostały zamienione miejscami. Ten sam argument 5 w pierwszej karcie ląduje w a, a w drugiej — w b.

private static int SubtractWrong(int b, int a) => a - b;
MathOperation operation = SubtractWrong;
int result = operation(5, 3);

SubtractWrong przyjmuje dwa int i zwraca int - sygnatura pasuje idealnie do MathOperation. Kompilator nie ma żadnego powodu, żeby zaprotestować.

Wywołanie jest jednak pozycyjne: pierwszy argument (5) ląduje w pierwszym parametrze metody, którym tu jest b, nie a. Drugi argument (3) ląduje w a. Wewnątrz metody liczymy a - b, czyli 3 - 5, czyli -2 — nie 2, jak sugerowałaby nazwa SubtractWrong wywołana analogicznie do Subtract.

To jest dokładnie odwrotność lekcji z poprzedniego wpisu. Tam różne nazwy przy jednym parametrze były bezpieczne. Tutaj, przy dwóch parametrach tego samego typu, różne nazwy mogą ukryć realny błąd logiczny a kompilator nie ma jak Cię ostrzec, bo dla niego to poprawny, w pełni zgodny z sygnaturą kod.

Zasada: 
przy delegacie z kilkoma parametrami tego samego typu nie ufaj nazwie metody, sprawdź nazwy jej parametrów i porównaj je z kolejnością w definicji delegata.

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

private static int AddThree(int a, int b, int c) => a + b + c;

MathOperation bad = AddThree;
// ❌ CS0123: No overload for 'AddThree' matches delegate 'MathOperation'

Trzy parametry zamiast dwóch. Ta sama zasada, którą znasz z poprzedniego wpisu przy metodzie Jump, tam różnica wynosiła zero kontra jeden, tutaj trzy kontra dwa. Liczba parametrów zawsze musi się zgadzać co do joty, niezależnie od tego, ile ich jest.

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

private static void PrintSum(int a, int b) => Console.WriteLine(a + b);

MathOperation bad2 = PrintSum;
// ❌ CS0407: 'void Pulapki.PrintSum(int, int)' has the wrong return type

PrintSum wypisuje sumę, ale jej nie zwraca, dokładnie ta sama różnica, co przy PrintGreeting z wpisu o delegacie zwracającym wartość. Więcej parametrów niczego tu nie zmienia: typ zwracany jest częścią kontraktu niezależnie od liczby wejść.

Ściągawka na code review

ZapisWynikDlaczego
MathOperation d = Subtract;(int, int) → int — kształt się zgadza
d(5, 3)✅ 2nazwy parametrów zgodne z sensem metody
MathOperation d = SubtractWrong;⚠️ ✅ kompiluje sięsygnatura pasuje, ale nazwy parametrów w metodzie są zamienione
d(5, 3) na SubtractWrong❌ -2zero błędu kompilacji, zły wynik logiczny
MathOperation d = AddThree;❌ CS0123trzy parametry zamiast dwóch
MathOperation d = PrintSum;❌ CS0407zwraca void zamiast int

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

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

Wartość pojawia się, gdy operacja jest wybierana dynamicznie,na przykład na podstawie tekstu wpisanego przez użytkownika, tak jak w kalkulatorze na telefonie: przycisk „=" nie wie, że dodaje, po prostu wywołuje aktualnie podpiętą operację z dwiema liczbami.

Ten kształt - kilka wejść, jedno wyjście to dokładnie to, co w nowoczesnym C# reprezentuje gotowy Func<int, int, int>: dwa pierwsze typy w liście to wejścia, ostatni to wyjście. Nie musisz już definiować własnego delegate, żeby zapisać ten sam kontrakt.

Podsumowanie

  • Więcej niż jeden parametr to ta sama mechanika - kompilator sprawdza listę typów pozycyjnie, długość listy nie ma znaczenia specjalnego.
  • Zamienione nazwy parametrów przy tym samym typie mogą dać zły wynik bez żadnego błędu kompilacji. To odwrotność lekcji o nazwach parametrów z poprzedniego wpisu.
  • Sygnatura to wciąż komplet: liczba parametrów, ich typy i typ zwracany - sprawdzany na dłuższej liście, ale według dokładnie tej samej zasady co przy jednym parametrze.
  • CS0123 i CS0407 wracają w tej samej roli co przy delegatach z jednym parametrem.
  • Func<int, int, int> to ten sam kontrakt co własny delegate int MathOperation(int a, int b), tylko bez własnej definicji typu.


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

Zadanie na 5 minut: 
zdefiniuj delegate decimal BinaryOperation(decimal a, decimal b);, napisz AddSubtract i Multiply, a potem zbuduj 
Dictionary<string, BinaryOperation> mapujący "+""-""*" na te metody - bez if/switch przy samym liczeniu wyniku.
Sprawdź operator, którego nie ma w słowniku, z TryGetValue zamiast wywołania null. Napisz w komentarzu, ile linijek musiałeś dotknąć, żeby dodać czwartą operację.

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