🤝 Дослідження еволюції делегатів у C#: Від версії 1.0 до сьогоднішнього дня ⏳

pic

Фото 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 ⏳

Leave a Reply

Your email address will not be published. Required fields are marked *