Фото Aron Visuals на Unsplash
Делегати в C# є потужними структурами, які дозволяють розглядати методи як змінні, зберігаючи посилання на методи з конкретними підписами. Як тип int
зберігає числа, а тип string
зберігає текст, так і delegate
зберігає блок коду, який можна викликати, коли це потрібно. З кожною новою версією C# були додані нові можливості для роботи з делегатами, що значно покращують функціональність.
Давайте подивимось, як ця функціональність розвивалася у різних версіях мови:
C# 1.0 — Делегати
Делегати були введені в C# 1.0 як спосіб інкапсуляції методів. Вони дозволяють зберігати методи як змінні і передавати їх як параметри.
Приклад:
public delegate void NotifyDelegate(string message);
public class Notifier
{
public static void SendEmail(string message)
{
Console.WriteLine("Email sent: " + message);
}
public static void ExecuteNotification(NotifyDelegate notify, string message)
{
notify(message);
}
static void Main()
{
ExecuteNotification(SendEmail, "You have a new message!");
}
}
Делегати також підтримують кілька посилань, створюючи так звані Мультикастові делегати (Multicast Delegates).
Приклад:
NotifyDelegate notifier = SendEmail;
notifier += SendSMS;
notifier("You have a new notification!");
public static void SendSMS(string message)
{
Console.WriteLine("SMS sent: " + message);
}
C# 2.0 — Події та Анонімні Делегати
У C# 2.0 були введені події (events) та анонімні делегати (anonymous delegates). Події інкапсулюють делегати і гарантують, що лише клас, який їх оголосив, може їх викликати.
Тим часом анонімні делегати дозволяють створювати вбудовані функції без необхідності використовувати іменовані методи.
Приклад події:
using System;
public class DownloadEventArgs : EventArgs
{
public string File { get; set; }
}
public class DownloadManager
{
public event EventHandler DownloadCompleted;
public void DownloadFile(string file)
{
Console.WriteLine($"Downloading {file}...");
System.Threading.Thread.Sleep(1000);
OnDownloadCompleted(file);
}
protected virtual void OnDownloadCompleted(string file)
{
DownloadCompleted?.Invoke(this, new DownloadEventArgs { File = file });
}
}
class Program
{
static void Main()
{
var manager = new DownloadManager();
manager.DownloadCompleted += manager_DownloadCompleted;
void manager_DownloadCompleted(object? sender, DownloadEventArgs e)
{
Console.WriteLine($"{e.File} download completed");
}
manager.DownloadFile("test.png");
}
}
Приклад анонімного делегата:
NotifyDelegate notifier = delegate(string message)
{
Console.WriteLine("Anonymous: " + message);
};
notifier("Hello from Anonymous Delegate!");
C# 3.0 — Лямбда-вирази, попередньо визначені універсальні делегати та дерева виразів
C# 3.0 представив лямбда-вирази (Lambda Expressions), дерева виразів (Expression Trees) та попередньо визначені універсальні делегати Func
, Action
та Predicate
, що ще більше спростило використання делегатів.
Лямбда-вирази (Lambda Expressions)
Це простіший та стислий спосіб записувати анонімні делегати.
Приклад:
NotifyDelegate notifier = message => Console.WriteLine("Lambda: " + message);
notifier("Hello from Lambda!");
Попередньо визначені універсальні делегати
У C# 3.0 деякі вбудовані делегати стали популярними, зокрема:
- Func: Для методів, що повертають значення, де останній тип — це тип повернення, а попередні — типи вхідних параметрів.
- Action: Для методів, що не повертають значення.
- Predicate: Для методів, що повертають булеві значення.
Приклад:
Func add = (a, b) => a + b;
Console.WriteLine(add(5, 3));
Action log = message => Console.WriteLine("Log: " + message);
log("Log entry");
Predicate isGreaterThanTen = number => number > 10;
Console.WriteLine(isGreaterThanTen(15));
Дерева виразів
Дерева виразів — це подання коду у вигляді дерева. Вони широко використовуються в LINQ і дозволяють динамічно маніпулювати та оцінювати вирази під час виконання програми.
Приклад дерева виразів з динамічною реалізацією фільтра:
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Linq;
class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
class Program
{
static void Main()
{
List products = new List
{
new Product { Name = "Product A", Price = 10 },
new Product { Name = "Product B", Price = 20 },
new Product { Name = "Product C", Price = 30 }
};
ParameterExpression param = Expression.Parameter(typeof(Product), "product");
Expression property = Expression.Property(param, "Price");
Expression constant = Expression.Constant(20M);
Expression filter = Expression.GreaterThan(property, constant);
var lambda = Expression.Lambda<Func<Product, bool>>(filter, param);
var func = lambda.Compile();
var results = products.Where(func);
foreach (var product in results)
{
Console.WriteLine(product.Name);
}
}
}
C# 9.0 — Статичні лямбда-вирази
У C# 9.0 була введена можливість визначати статичні лямбда-вирази (Static Lambdas). Статичні лямбда-вирази не захоплюють змінні з зовнішнього контексту, що може покращити продуктивність і зменшити споживання пам'яті. Коли ви використовуєте модифікатор static
в лямбда-виразі, ви запобігаєте його доступу до локальних змінних або членів екземпляра зовнішнього контексту.
Це робить лямбду легшою, оскільки їй не потрібно створювати посилання на зовнішній контекст.
Приклад:
Func square = static x => x * x;
Console.WriteLine(square(5));
У наведеному вище прикладі лямбда static x => x * x
не може отримати доступ до жодних змінних із зовнішнього контексту. Це дозволяє уникнути зайвих захоплень змінних та покращує продуктивність.
C# 10.0 — Виведення типів та атрибути в лямбдах
C# 10.0 внесла значні покращення для лямбда-виразів, зокрема виведення типу повернення та підтримку атрибутів.
Виведення типу в лямбдах
До C# 10.0 лямбди повинні були визначати свій тип повернення на основі делегата або інтерфейсу Func/Action/Predicate
. У C# 10.0 компілятор може безпосередньо вивести тип повернення.
Приклад:
var square = (int x) => x * x;
Console.WriteLine(square(4));
Компілятор виводить тип square
як Func
.
Атрибути в лямбдах
Тепер можна додавати атрибути безпосередньо до лямбда-виразів.
Приклад:
var lambda = [Obsolete("Use another method")] (int x) => x * x;
Console.WriteLine(lambda(4));
Це дозволяє додавати додаткову метаінформацію до лямбд, таку як попередження про застарілість, валідацію або описи.
Висновок
Делегати значно еволюціонували з моменту їх введення в C# 1.0. З появою лямбда-виразів, дерев виразів, атрибутів та виведення типів, вони стали надзвичайно гнучким та потужним інструментом. Кожна нова версія C# приносила покращення, що роблять код більш зрозумілим, ефективним та виразним.
Освоївши делегати, лямбда-вирази та їх сучасні варіації, ви будете готові вирішувати складні завдання та створювати елегантні рішення в екосистемі .NET.
Практикуйте показані приклади, досліджуйте різні сценарії та продовжуйте вчитися!
Перекладено з: 🤝 Exploring the Evolution of Delegates in C#: From Version 1.0 to the Present ⏳