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("Hi!"); };
Передача 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); });
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ