Передавайте Состояние. Начало
Представьте, что вы создали простую функцию фильтрации:
public IEnumerable<Item>
GetOversized(IEnumerable<Item> items)
=> items.Where(item => item.Size > 45);
А затем решили, что нужно добавить параметр. Интуиция предлагает простое решение:
public IEnumerable<Item>
GetOversized(IEnumerable<Item> items, int minSize)
=> items.Where(item => item.Size > minSize);
Для фрагментов кода, которые выполняются редко, это будет работать. А на горячем пути? Или в параллельном коде с отменой или продолжением? Давайте разберёмся!
Лямбда-функции
C# позволяет нам определить анонимную функцию, которую можно передать как Func или Action. Сначала определяем параметры, а потом тело функции после стрелки:
item => item.Size > minSize;
Есть одна проблема: эти функции захватывают контекст, т.е. переменные или параметры из окружения. В реальности компилятор создаёт класс, поля которого содержат необходимые данные (в примере выше – переменную minSize).
Это может показаться не такой уж большой проблемой, но, если подобные случаи накапливаются на горячем пути, можно столкнуться с проблемами выделения памяти.
В C# 9.0 добавлены статические анонимные функции, которые запрещают захват контекста. Однако, в примере выше это приведёт к ошибке компиляции:
// Нельзя использовать static и minSize вместе
items.Where(static (item) => item.Size > minSize);
Не всегда возможно избежать захвата контекста. Для использования статических анонимных функций необходимо выполнение одного из двух условий:
1. Функция требует только предоставленных параметров.
2. Существует перегрузка, которая принимает состояние, передаваемое в качестве дополнительного параметра.
Рассмотрим второе условие подробнее:
public IEnumerable<T> Query(Func<T, bool> predicate);
Очевидно, что, если для предиката требуется какое-либо внешнее состояние, потребуется захватить его. А в следующей перегрузке:
public IEnumerable<T> Query<TContext>(
Func<T, TContext, bool> predicate,
TContext ctx);
…добавлен дополнительный параметр, называемый контекстом, который можно передать функции. Суть в том, что он также передается предикату. Если у вас есть чётко определённый контекст, вы можете просто передать его в качестве дополнительного параметра:
repo.Query(
// ctxMinSize передаст параметр minSize в лямбду
static (item, ctxMinSize) =>
(item) => item.Size > ctxMinSize,
minSize);
Теперь мы знаем, что, объединив статические лямбда-функции и параметр контекста, мы можем многого добиться, не захватывая состояние (не выделяя память). Далее рассмотрим примеры.
Продолжение следует…
Источник: https://blog.scooletz.com/2025/10/08/pass-the-state