Delegat z wieloma parametrami w C# – pułapka bez błędu

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 -2. Kompilator 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
Dwa wejścia, jedno wyjście — krok po kroku
Powstaje typ. Kontraktem jest (int, int) → int — dwa wejścia, jedno wyjście.
Bez nawiasów. Kompilator sprawdza tu całą listę typów.
Z nawiasami, z dwoma argumentami, w tej samej kolejności co w definicji.
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
W 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
Ta sama sygnatura, inny 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 typePrintSum 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
| Zapis | Wynik | Dlaczego |
|---|---|---|
MathOperation d = Subtract; | ✅ | (int, int) → int — kształt się zgadza |
d(5, 3) | ✅ 2 | nazwy 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 | ❌ -2 | zero błędu kompilacji, zły wynik logiczny |
MathOperation d = AddThree; | ❌ CS0123 | trzy parametry zamiast dwóch |
MathOperation d = PrintSum; | ❌ CS0407 | zwraca 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łasnydelegate 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 Add, Subtract 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
- Delegat z parametrem w C# — dlaczego nazwa parametru nic nie znaczy, dopóki nie ma z czym jej pomylić
- Delegat zwracający wartość w C# — pułapka bez ostrzeżenia kompilatora, cz. 1
- Func i Action w C# — gotowe delegaty zamiast własnych
- LINQ w C# — kompletny przewodnik — delegaty z wieloma parametrami 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ę →