🔍Вони також потребують представлення! Давайте ближче подивимося на делегати.

C# мові делегат (delegate) можна визначити як “тип безпечного для типів вказівника на функцію, який може вказувати на один або декілька методів”. Це нагадує концепцію вказівників на функції в C та C++, але головна відмінність полягає в тому, що C# забезпечує типову безпеку в керованому (managed) середовищі. Це дозволяє зменшити проблеми, такі як несумісність підписів (типи параметрів і повернених значень) або помилки пам'яті.

У підсумку: Delegate зберігає підпис методу (типи параметрів + тип повернення) і дозволяє вибрати метод для виклику під час виконання програми.

🤔 Чому делегати так важливі?

  1. Механізм зворотного виклику (Callback): Ви можете використовувати делегати для визначення, які методи будуть виконані після завершення довготривалої операції. Наприклад, якщо завантажується файл, можна викликати метод для показу повідомлення після завершення завантаження.
  2. Система подій (Event System): Основою концепції подій в C# є делегати. Події, які повинні спрацьовувати при натисканні кнопки в користувацькому інтерфейсі (UI) або після завершення асинхронної операції, реалізуються за допомогою делегатів.
  3. Стратегія (Strategy Pattern): Ви можете вибирати алгоритми в залежності від введення користувача через делегат. Наприклад, якщо у вас є різні стратегії запису на диск, делегат може визначати, яка з них буде використана в процесі виконання.
  4. Зрозумілий і гнучкий код: Можливість передавати методи, як параметри, підвищує модульність коду. Лямбда-вирази і LINQ також ґрунтуються на цьому підході.

🪄 Оголошення делегатів і приклади

  1. Як оголосити делегат?

Делегат зазвичай оголошується в просторі імен (поза класом):

public delegate int MyMathDelegate(int x, int y); // Делегат, який представляє метод, що приймає два int і повертає int

Це просте оголошення створює спеціальний тип (MyMathDelegate), що має підпис (int, int) -> int.

  1. Як прив'язати метод до делегата?
public delegate int MyMathDelegate(int x, int y);  

class Program  
{  
 static void Main()  
 {  
 // Створюємо приклад делегата  
 MyMathDelegate operation;  

 // 1) Прив'язуємо до методу додавання  
 operation = Add;  
 int sum = operation(5, 3); // 8  
 Console.WriteLine($"Сума: {sum}");  

 // 2) Прив'язуємо до методу множення  
 operation = Multiply;  
 int product = operation(5, 3); // 15  
 Console.WriteLine($"Добуток: {product}");  
 }  

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

Як бачите, спочатку метод Add підключений до operation, а в наступному рядку operation викликає метод Multiply. Вся ця гнучкість дозволяє виконувати перехід між методами з підписами, що збігаються, на етапі виконання (runtime).

⚡ Мульти-методні делегати (Multicast Delegates)

Делегат може викликати кілька методів (multi-cast). Ці методи виконуються у вигляді "ланцюга викликів".

public delegate void NotifyDelegate(string message);  

class Program  
{  
 static void Main()  
 {  
 NotifyDelegate notify = MethodA;  
 notify += MethodB;  
 notify += MethodC;  

 notify("Привіт");   
 // Викликаються MethodA -> MethodB -> MethodC по черзі.  
 }  

 static void MethodA(string msg) => Console.WriteLine("A каже: " + msg);  
 static void MethodB(string msg) => Console.WriteLine("B каже: " + msg);  
 static void MethodC(string msg) => Console.WriteLine("C каже: " + msg);  
}

У ланцюгу викликів, якщо тип повернення — це void, то проблем немає, всі методи виконуються послідовно. Однак якщо тип повернення вимагає значення (наприклад, int), то зазвичай використовується результат останнього доданого методу. Тому, якщо у вас є важливе значення повернення, більш безпечно використовувати делегат, який прив'язаний лише до одного методу, замість мульти-методного підходу.

🔑 Готові типи делегатів: Action, Func, Predicate

Мова C# надає готові generic типи делегатів для найпоширеніших підписів. Це дозволяє не створювати власні делегати для багатьох випадків.

1.

Action

  • Представляє методи, що мають тип повернення void і можуть приймати від 0 до 16 параметрів.
  • Наприклад: Action для методів, що приймають один параметр типу string і повертають void.

Func

  • Представляє методи, які повертають значення. Можуть приймати від 0 до 16 параметрів.
  • Наприклад: Func для методів, що приймають два параметри типу int і повертають значення типу int.

Predicate

  • Приймає один параметр і повертає значення типу bool.
  • Наприклад: Predicate для методів, що приймають параметр типу int і повертають true/false.
// Action  
Action greet = (name) => Console.WriteLine("Привіт " + name);  
greet("Ахмет"); // "Привіт Ахмет"  

// Func  
Func<int, int, int> add = (a, b) => a + b;  
Console.WriteLine(add(2, 3)); // 5  

// Predicate  
Predicate<int> isEven = x => x % 2 == 0;  
Console.WriteLine(isEven(10)); // True  
Console.WriteLine(isEven(11)); // False

Ці готові типи дуже корисні, особливо в LINQ та при використанні Лямбда-виразів.

📌 Анонімні методи та лямбда-вирази

Користуючись делегатами, замість того, щоб визначати ім’я методу, можна написати анонімний метод або лямбда-вираз (arrow function).

Анонімний метод:

Action helloDelegate = delegate  
{  
 Console.WriteLine("Привіт, Світ!");  
};  

helloDelegate(); // "Привіт, Світ!"

Лямбда-вираз:

Action helloLambda = (name) =>   
{  
 Console.WriteLine($"Привіт, {name}!");  
};  

helloLambda("Зейнеп");

🎉 Взаємозв'язок делегатів і подій

У C# структура event є особливим використанням делегатів. До події можна підписуватися тільки за допомогою += та -=; це запобігає випадковому виклику події ззовні.

public delegate void ThresholdReachedHandler(int currentValue);  

public class Counter  
{  
 public event ThresholdReachedHandler ThresholdReached;  

 private int total = 0;  
 private int threshold = 10;  

 public void Add(int value)  
 {  
 total += value;  
 if (total >= threshold)  
 {  
 // Викликаємо подію, перевіряючи на null за допомогою "?"  
 ThresholdReached?.Invoke(total);  
 }  
 }  
}

Приклад використання:

var counter = new Counter();  
counter.ThresholdReached += (val) => Console.WriteLine($"Ліміт перевищено! Значення: {val}");  
counter.Add(5);  
counter.Add(6); // Подія спрацьовує: 11 >= 10

У подіях принцип роботи делегатів працює "за кулісами"; подія просто надає суворіші обмеження доступу.

🤿 Для зацікавлених: більш глибоке використання та важливі моменти

  1. Управління виключеннями: Якщо один із методів в мульти-методному делегаті викликає помилку, решта методів зазвичай не виконуються. Тому, коли ви створюєте ланцюг методів, можливо, вам буде потрібно обробляти помилки окремо для кожного методу за допомогою try-catch.
  2. Продуктивність: Виклик делегата може бути трохи дорожчим за виклик звичайного методу. Однак у більшості випадків це не має значення. Для продуктивних додатків варто проводити вимірювання (профілювання).
  3. Ризик витоків пам'яті: Якщо ви підписуєтеся на події (+=), обов'язково відписуйтеся (-=) після завершення роботи, щоб уникнути неочікуваного збереження об'єктів у пам'яті (затримка збирання сміття).
  4. Асинхронне використання: Делегати за замовчуванням працюють синхронно. Якщо вам потрібно асинхронне виконання, використовуйте async/await, Task або підходи з використанням Thread/ThreadPool.
  5. Delegate та Expression Trees: У складніших сценаріях ви можете створювати делегати через expression tree і компілювати їх.
    Bu, dinamik kod üretimi ve metot çağrılarında esneklik sunar.

🎯 Узагальнення…

  • Оволодівши структурою Delegate, ви зможете легко створювати архітектури, що базуються на callback та подіях.
  • Готові типи делегатів, такі як Action, Func та Predicate, скорочують код та пропонують стандартну структуру.
  • Використовуючи multicast delegate (ланцюг викликів), звертайте увагу на типи повернення та управління помилками.
  • Події — це більш контрольована форма використання делегатів, і вони часто складають основу подійних архітектур у великих проектах.

Після того, як ви освоїте концепцію делегатів, ви зможете набагато легше працювати з такими сферами, як асинхронне програмування, шаблони проектування (особливо Strategy та Observer) та LINQ запити.

📚 Додаткові ресурси

Перекладено з: 🔍Onların da Temsil Edilme İhtiyacı Var! Delegate’e DahaYakından Bakalım.

Leave a Reply

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