1. Вступ
Коли ви програмуєте, іноді потрібно описати просту дію, яку використаєте рівно один раз, і більше вона ніде не знадобиться. Створювати для цього окремий метод — це як забивати цвях молотком із набору для складання літака. Значно простіше — взяти щось підручне й розв’язати завдання «на місці».
Анонімний метод — це тимчасовий шматочок коду, якому не потрібне імʼя, бо він використовується прямо там, де створюється. Він:
- Оголошується всередині іншого методу.
- Не має імені.
- Зазвичай застосовується один раз — наприклад, передається делегату або підписується на подію.
Нині частіше використовують лямбда-вирази (оператор =>), але розуміння анонімних методів допомагає розібратися, як влаштовані делегати та подієва модель у C#.
Коли анонімний метод — це зручно?
- Потрібно швидко передати шматок логіки як параметр — наприклад, для сортування, фільтрації, обробки подій тощо.
- Немає сенсу створювати окремі методи в класі заради одноразової логіки.
- Коли хочеться, щоб код був компактним і легко читався: уся «маленька бізнес-логіка» — в одному місці.
Трішки історії
Вперше у C# можливість створювати анонімні методи з’явилася у версії 2.0. До того з делегатами працювати було громіздко: доводилося оголошувати окремий іменований метод, навіть якщо його логіку застосовували лише в одному місці. З появою анонімних методів усе спростили, а з приходом лямбда-виразів ця ідея вийшла на новий рівень лаконічності.
2. Класика жанру: делегати і звичний «іменований» метод
Перш ніж знайомитися з анонімними методами, згадаємо стандартний спосіб передавання поведінки через делегати. Припустімо, у нас є застосунок «База книжок», де ми хочемо фільтрувати книжки за різними умовами.
// Визначаємо делегат
public delegate bool BookFilter(Book book);
// Клас книжки
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
Раніше ми писали так:
// Окрема функція-фільтр
public static bool IsClassic(Book b)
{
return b.Year < 1970;
}
// Десь у коді використання
BookFilter filter = IsClassic;
Щоразу створювати окремий метод — не завжди зручно. А якщо фільтрів — десятки?
3. Анонімний метод: мінімалізм, дружба з делегатами
Анонімний метод дозволяє визначити код-фільтр прямо там, де він потрібен:
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
Ось і все! Жодних зайвих методів — усе прямо на місці. delegate виступає як оголошення безіменної функції.
Загальний синтаксис
delegate([аргументи])
{
// тіло методу
};
Приклад використання в програмі:
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
public delegate bool BookFilter(Book book);
class Program
{
static void Main()
{
Book[] books = {
new Book { Title = "Майстер і Маргарита", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
// Анонімний фільтр для класиків
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
foreach (Book book in books)
{
if (filter(book)) // викликаємо анонімну функцію!
Console.WriteLine($"{book.Title} — класика!");
}
}
}
Виведення:
Майстер і Маргарита — класика!
4. Анонімний метод з різними делегатами
Анонімні методи чудово працюють з усіма делегатами. Наприклад, зі стандартними Action і Func<T, TResult>, які ми детально розглянемо далі.
Action<string> sayHello = delegate(string name)
{
Console.WriteLine($"Привіт, {name}!");
};
sayHello("Світ"); // Привіт, Світ!
Коротка ілюстрація: як працює «одноразовий» анонімний метод
Найпростіше уявити анонімний метод як тимчасового працівника: з’явився на коротке завдання, виконав — і зник. Зв’язуємо працівника й завдання через делегат.
Func<int, int, int> sum = delegate (int a, int b) {
return a + b;
};
Console.WriteLine(sum(5, 7)); // 12
Застосування: сортування, пошук, обробка колекцій
Припустімо, у нас є список книжок, який ми хочемо відсортувати за роком видання. Оголошувати окремий метод заради одноразового порівняння — зайве.
var books = new List<Book>
{
new Book { Title = "Майстер і Маргарита", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
books.Sort(delegate(Book a, Book b)
{
return a.Year.CompareTo(b.Year);
});
foreach (var book in books)
Console.WriteLine($"{book.Title} ({book.Year})");
5. Корисні нюанси
Анонімний метод — це не те саме, що лямбда-вираз?
У сучасному C# частіше використовують лямбда-вирази (=>), і зазвичай між ними мало різниці, проте принципові відмінності все ж є:
- Лямбда-вирази коротші й виразніші.
- У лямбд — однозначні правила захоплення змінних (ми це розглянемо трохи згодом).
- Анонімні методи з’явилися раніше й інколи трапляються у «спадщині» старих проєктів.
Так виглядав би наш приклад на лямбді:
BookFilter filter = b => b.Year < 1970;
Втім, знати, як працює і старий синтаксис, і новий — корисно для співбесід, читання чужого коду та глибокого розуміння делегатів.
Анонімні методи з параметрами і без параметрів
Якщо делегат нічого не приймає, його можна записати в один рядок:
Action printHello = delegate { Console.WriteLine("Привіт!"); };
printHello(); // Привіт!
Якщо приймає параметри — його теж можна записати в один рядок:
Action<int> printSquare = delegate (int x) { Console.WriteLine(x * x); };
printSquare(6); // 36
Анонімний метод і лямбда-вираз
| Анонімний метод | Лямбда-вираз | |
|---|---|---|
| Синтаксис | |
|
| Захоплення змінних | Так | Так |
| Популярність | Використовується рідко | Стандарт |
| Повернення значення | Може/повинен | Може/повинен |
| Багаторядковість | Можна | Можна |
6. Анонімні методи — дрібниці й нюанси використання
Захоплення локальних змінних
Анонімні методи можуть використовувати змінні із зовнішнього методу — цей механізм називається «замикання». Наприклад:
int minYear = 1970;
BookFilter filter = delegate(Book book)
{
return book.Year < minYear;
};
Console.WriteLine(filter(new Book { Title = "Test", Year = 1960 })); // True
Якщо пізніше змінити minYear, фільтр використовуватиме нове значення!
Можна не вказувати параметри
Якщо параметри не потрібні:
Action sayHi = delegate { Console.WriteLine("Привіт!"); };
Передача null
Якщо делегату не призначили метод (наприклад, анонімний), його значення — null, і спроба виклику призведе до NullReferenceException. Будьте обережні.
Багаторядковість
Анонімний метод може містити повноцінний блок коду, умови, цикли та навіть інші виклики:
Action manyThings = delegate
{
Console.WriteLine("Почали!");
for (int i = 0; i < 3; i++)
Console.WriteLine(i);
Console.WriteLine("Закінчили!");
};
manyThings();
7. Типові помилки та особливості
Іноді розробники плутають область видимості змінних: захоплення значень із циклу або вкладеного методу може призвести до несподіваних результатів.
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(delegate { Console.WriteLine(i); });
}
foreach (var action in actions) action(); // 3 3 3 — несподіванка!
Річ у тім, що анонімний метод «бачить» змінну i — коли цикл закінчився, i стало 3. Усі методи друкують одне й те саме. Для коректної поведінки краще захопити змінну так:
for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(delegate { Console.WriteLine(current); });
}
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ