Cześć! Dzisiaj porozmawiamy o delegatach w C#. Jest to temat, który na pierwszy rzut oka może wydawać się skomplikowany, ale naprawdę warto go zrozumieć. Delegaty to potężny mechanizm umożliwiający przekazywanie metod niczym zmiennych. Mogą one znacząco zwiększyć elastyczność naszego kodu i otworzyć wiele możliwości, które pozwolą nam tworzyć bardziej przejrzyste i rozszerzalne aplikacje.
Co to są delegaty?
Delegat to po prostu typ, który może przechowywać referencję do metody o określonej sygnaturze (czyli zwracanym typie i listy parametrów). Można go sobie wyobrazić jako "uchwyt" do metody, który można przekazywać jak zwykłą zmienną. Dzięki temu, w różnych miejscach aplikacji możemy dynamicznie decydować, która metoda zostanie wywołana.
Prosty przykład użycia delegata
Zacznijmy od bardzo prostego przykładu. Załóżmy, że mamy aplikację konsolową w C#, w której chcemy operować na liczbach, ale nie wiemy, czy chcemy je dodawać, odejmować czy mnożyć. Możemy stworzyć delegat, który będzie wskazywał na konkretną metodę, zależnie od potrzeb.
using System;
namespace DelegatesDemo
{
// Definicja delegata
public delegate int MathOperation(int x, int y);
class Program
{
static void Main(string[] args)
{
// Przypisujemy do delegata metodę Add
MathOperation operation = Add;
Console.WriteLine("Dodawanie 5 + 3 = " + operation(5, 3));
// Teraz możemy zmienić zachowanie w locie
operation = Subtract;
Console.WriteLine("Odejmowanie 5 - 3 = " + operation(5, 3));
}
// Metoda do dodawania
public static int Add(int x, int y) => x + y;
// Metoda do odejmowania
public static int Subtract(int x, int y) => x - y;
}
}
W powyższym przykładzie MathOperation to nazwa naszego delegata. Jest to typ, który przechowuje metody zwracające int i przyjmujące dwa parametry typu int. Dzięki temu w Main możemy w dowolnym momencie zmienić, która metoda faktycznie zostanie wywołana przez delegat operation.
Delegaty wielokrotne (multicast)
Delegaty w C# mają jeszcze jedną ciekawą cechę – mogą wskazywać na wiele metod jednocześnie. Możemy dodać kolejną metodę do delegata za pomocą operatora +=. Taki delegat wywoła wszystkie przypisane metody w kolejności, w jakiej zostały dodane.
using System;
namespace DelegatesDemo
{
public delegate void LogOperation(string message);
class Program
{
static void Main(string[] args)
{
LogOperation logOps = LogToConsole;
logOps += LogToFile;
// Wywołają się dwie metody: LogToConsole i LogToFile
logOps("Start aplikacji");
}
static void LogToConsole(string msg)
=> Console.WriteLine($"Konsola: {msg}");
static void LogToFile(string msg)
{
// Dla uproszczenia wypisujemy w konsoli
// W praktyce zapis do pliku
Console.WriteLine($"Plik: {msg}");
}
}
}
Powyższy kod pokazuje, jak delegat logOps może wywołać dwie różne metody logujące. Dzięki temu nie trzeba pisać osobnej logiki do konsoli i pliku w jednej metodzie – można je rozdzielić i decydować, które zostaną wywołane wspólnie.
Delegaty w praktyce – przekazywanie funkcji jako parametru
Bardzo często delegaty wykorzystuje się do przekazywania metody jako parametru do innej metody. Dzięki temu można napisać jedną ogólną funkcję, która wykonuje jakąś czynność na danych, a szczegółowa logika (np. sposób przetwarzania danych) jest przekazywana jako delegat.
using System;
using System.Collections.Generic;
namespace DelegatesDemo
{
public delegate bool Filter<T>(T item);
class Program
{
static void Main(string[] args)
{
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// Przekazujemy metodę IsEven jako delegat
var evenNumbers = FilterList(numbers, IsEven);
Console.WriteLine("Parzyste liczby:");
foreach (var n in evenNumbers)
{
Console.WriteLine(n);
}
}
// Metoda przyjmuje listę i delegat do filtrowania
static List<int> FilterList(List<int> list, Filter<int> filter)
{
var result = new List<int>();
foreach (var item in list)
{
if (filter(item))
{
result.Add(item);
}
}
return result;
}
static bool IsEven(int x) => x % 2 == 0;
}
}
Tutaj Filter<int> to delegat definiujący metodę, która przyjmuje liczbę typu int i zwraca bool (czyli prawda/fałsz). Nasza metoda FilterList może teraz filtrować listę według dowolnego kryterium, które przekażemy jako delegat.
Wbudowane delegaty: Action, Func i Predicate
Aby uprościć korzystanie z delegatów, w C# mamy kilka wbudowanych typów delegatów:
• Action – reprezentuje metodę, która nic nie zwraca. Może przyjmować parametry, np. Action<int, string>.
• Func – reprezentuje metodę, która coś zwraca. Ostatni parametr generyka określa typ zwracany. Przykład: Func<int, int, int> to delegat metod z dwoma parametrami int i zwracający int.
• Predicate<T> – to specjalny przypadek Func<T, bool>, służy do metod zwracających wartość typu bool.
Dzięki tym wbudowanym delegatom często nie musimy definiować własnych typów. Na przykład, wcześniejszy przykład filtrowania można uprościć stosując Predicate<int> zamiast samodzielnie definiować public delegate bool Filter<T>(T item);.
Praktyczne zastosowania delegatów
• Eventy w C#: Delegaty są podstawą do tworzenia eventów, np. w Windows Forms, WPF czy przy korzystaniu z różnych bibliotek.
• Programowanie funkcyjne: Wiele metod w LINQ przyjmuje delegaty (Func<T, bool>, Func<T, TResult>), co pozwala nam tworzyć potężne zapytania i operacje na kolekcjach.
• Wstrzykiwanie zależności: Możemy przekazywać konkretne implementacje metod np. do klas serwisów, co zwiększa modularność aplikacji.
Podsumowanie
Podsumowując, delegaty w C# umożliwiają nam przekazywanie metod jako parametrów, łączenie wielu metod w jedną listę wywołań (multicast) oraz rozszerzanie naszej aplikacji bez konieczności modyfikowania istniejących fragmentów kodu. To niezwykle przydatny i elastyczny mechanizm, z którego korzysta się w wielu miejscach platformy .NET, choćby w eventach czy LINQ.
Mam nadzieję, że te przykłady pomogły Ci zrozumieć, czym są delegaty i jak je stosować w praktyce. Oczywiście świat .NET jest ogromny, a to dopiero wierzchołek góry lodowej. Jeżeli chcesz uporządkować swoją wiedzę i przejść ze mną przez serię ćwiczeń i praktycznych projektów, to serdecznie zapraszam Cię do mojego szkolenia online "Zostań Programistą .NET". Znajdziesz tam kompleksowy materiał, który przeprowadzi Cię przez podstawy i bardziej zaawansowane zagadnienia w C# i .NET, tak abyś pewnie mógł budować nowoczesne aplikacje i mógł zacząć pracę jako Młodszy Programista C#/.NET.
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.