Что такое Interceptor (перехватчик)?

Ответ

Interceptor (Перехватчик) — это паттерн проектирования, который позволяет перехватывать вызовы методов или обработку запросов для добавления сквозной функциональности (cross-cutting concerns) без модификации основного кода бизнес-логики.

Основные цели:

  • Логирование вызовов методов и их параметров.
  • Кэширование результатов дорогостоящих операций.
  • Валидация и авторизация входящих данных.
  • Управление транзакциями (открытие/закрытие, откат).
  • Измерение производительности (профилирование).

Реализация в C#: Чаще всего реализуется через:

  1. Аспектно-ориентированное программирование (AOP) с использованием библиотек (Castle DynamicProxy, PostSharp).
  2. Паттерн Декоратор для оборачивания сервисов.
  3. Фильтры в ASP.NET Core (Action Filters, Resource Filters).

Практический пример с Castle.DynamicProxy:

using Castle.DynamicProxy;

// 1. Определяем перехватчик
public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine($"[LOG] Вызов метода: {invocation.Method.Name}");
        Console.WriteLine($"[LOG] Аргументы: {string.Join(", ", invocation.Arguments)}");

        var watch = Stopwatch.StartNew();
        invocation.Proceed(); // Продолжаем выполнение оригинального метода
        watch.Stop();

        Console.WriteLine($"[LOG] Метод выполнен за {watch.ElapsedMilliseconds} мс");
        Console.WriteLine($"[LOG] Результат: {invocation.ReturnValue}");
    }
}

// 2. Создаем прокси-объект с перехватчиком
public interface IService { void ProcessData(string data); }
public class MyService : IService 
{ 
    public virtual void ProcessData(string data) { /* Логика */ } 
}

var generator = new ProxyGenerator();
IService proxy = generator.CreateClassProxy<MyService>(new LoggingInterceptor());

// 3. Вызов будет перехвачен
proxy.ProcessData("test");

Ключевые нюансы:

  • Для перехвата методов классов (не интерфейсов) методы должны быть помечены как virtual.
  • Создание прокси и перехват вносят небольшие накладные расходы на производительность.
  • Важно избегать побочных эффектов в перехватчиках, которые могут нарушить основную логику.

Ответ 18+ 🔞

Смотри, есть такая штука — Interceptor, или по-нашему "перехватчик". Это как подслушивающее устройство для твоего кода, только легальное. Суть в чём: ты можешь вклиниться в вызов любого метода и сделать что-то своё — залогировать, закэшировать, проверить права — и всё это без правок в самой бизнес-логике. Красота же, да?

Зачем это вообще нужно, спросишь? Да обычная история:

  • Логирование — чтобы потом, когда всё ебнулось, не гадать, что за аргументы пришли и что вернулось.
  • Кэширование — если метод жрёт ресурсы как не в себя, можно результат припрятать.
  • Валидация и авторизация — отсеять хуйню и непрошенных гостей до того, как они доберутся до ядра.
  • Транзакции — автоматически открывать и закрывать эту канитель.
  • Замер производительности — понять, какой метод тормозит как черепаха в патруле.

Как это делается в C#? Есть несколько путей, но самые популярные:

  1. Аспекты (AOP) — через библиотеки вроде Castle DynamicProxy или PostSharp. Это самый мощный и правильный способ, но надо подключать зависимости.
  2. Декоратор — старый добрый паттерн, когда оборачиваешь сервис в другой класс. Работает, но плодит кучу обёрток, если функционала много.
  3. Фильтры в ASP.NET Core — если речь про веб, то это родной и удобный инструмент (Action Filters, Resource Filters).

Давай на живом примере через Castle.DynamicProxy, чтобы понятно стало:

using Castle.DynamicProxy;

// 1. Пишем самого перехватчика. Он как шпион в штатском.
public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        // Ловим вызов на входе
        Console.WriteLine($"[LOG] Вызов метода: {invocation.Method.Name}");
        Console.WriteLine($"[LOG] Аргументы: {string.Join(", ", invocation.Arguments)}");

        // Засекаем время, вдруг медленно работает
        var watch = Stopwatch.StartNew();

        // Ключевой момент! Пускаем вызов дальше, к реальному методу.
        invocation.Proceed();

        watch.Stop();

        // Смотрим, что получилось на выходе
        Console.WriteLine($"[LOG] Метод выполнен за {watch.ElapsedMilliseconds} мс");
        Console.WriteLine($"[LOG] Результат: {invocation.ReturnValue}");
    }
}

// 2. Готовим сервис, который будем перехватывать.
public interface IService { void ProcessData(string data); }

// ВАЖНО! Методы в классе должны быть virtual, иначе DynamicProxy не подцепится.
public class MyService : IService 
{ 
    public virtual void ProcessData(string data) 
    { 
        /* Тут твоя святая бизнес-логика */ 
    } 
}

// 3. Магия создания прокси.
var generator = new ProxyGenerator();
// Подсовываем перехватчик нашему сервису
IService proxy = generator.CreateClassProxy<MyService>(new LoggingInterceptor());

// 4. Вызываем. И вуаля — наш шпион уже всё видит и пишет в консоль.
proxy.ProcessData("test");

На что обратить внимание, а то обожжёшься:

  • Методы должны быть virtual. Это обязательное условие для перехвата классов (для интерфейсов — нет). Если забудешь, перехватчик просто проигнорирует вызов, и будешь потом чесать репу.
  • Производительность. Создание прокси и сам перехват — это дополнительные телодвижения. В 99% случаев пофигу, но если у тебя там ядрёная высоконагруженная система, надо мерить.
  • Побочные эффекты. В перехватчике нельзя делать дичь, которая сломает основную логику. Типа, не вздумай глобально менять аргументы или возвращаемое значение без серьёзной причины, а то потом отладка превратится в ад.