JavaRush /Курси /C# SELF /Знайомство з анонімними методами (

Знайомство з анонімними методами ( delegate)

C# SELF
Рівень 49 , Лекція 0
Відкрита

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

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

Анонімний метод Лямбда-вираз
Синтаксис
delegate (...) {}
(args) => {}
Захоплення змінних Так Так
Популярність Використовується рідко Стандарт
Повернення значення Може/повинен Може/повинен
Багаторядковість Можна Можна

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); });
}
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ