С#: Элегантный способ обертывания вызовов метода

Извинения за довольно неоднозначный заголовок, но то, что я пытаюсь достичь, вероятно, лучше указано в коде.

У меня есть WCF-клиент. Когда я вызываю методы, я хотел бы объединить каждый вызов в некоторый код обработки ошибок. Таким образом, вместо непосредственного использования методов я создал следующую вспомогательную функцию в классе клиента:

    public T HandleServiceCall<T>(Func<IApplicationService, T> serviceMethod)
    {
        try
        {
            return serviceMethod(decorator);
        }
        [...]
    }

И клиентский код использует его следующим образом:

service.HandleServiceCall(channel => channel.Ping("Hello"));

И вызов Ping красиво завернут в какую-то логику, которая попытается обработать любые ошибки.

Это отлично работает, за исключением того, что у меня теперь есть требование узнать, какие методы на самом деле вызываются в службе. Первоначально я надеялся просто осмотреть Func<IApplicationService, T>, используя деревья выражений, но не очень далеко.

Наконец, я остановился на шаблоне Decorator:

    public T HandleServiceCall<T>(Func<IApplicationService, T> serviceMethod)
    {
        var decorator = new ServiceCallDecorator(client.ServiceChannel);
        try
        {
            return serviceMethod(decorator);
        }
        [...]
        finally
        {
            if (decorator.PingWasCalled)
            {
                Console.Writeline("I know that Ping was called")
            }
        }
    }

И сам Decorator:

    private class ServiceCallDecorator : IApplicationService
    {
        private readonly IApplicationService service;

        public ServiceCallDecorator(IApplicationService service)
        {
            this.service = service;
            this.PingWasCalled = new Nullable<bool>();
        }

        public bool? PingWasCalled
        {
            get;
            private set;
        }

        public ServiceResponse<bool> Ping(string message)
        {
            PingWasCalled = true;
            return service.Ping(message);
        }
    }

Это действительно неуклюжий и довольно много кода. Есть ли более элегантный способ сделать это?

Ответ 1

Вы можете использовать выражение, а затем проверить тело.

Что-то вроде

public T HandleServiceCall<T>(Expression<Func<IApplicationService, T>> serviceMethod)     
{         
    try         
    {          
        var func = serviceMethod.Compile();
        string body = serviceMethod.Body.ToString();
        return func(new ConcreteAppService()); 
    }        
    catch(Exception ex)
    {
        ...     
              }
}

Ответ 2

Считаете ли вы использование аспектно-ориентированного подхода? Это похоже на то, что вам нужно.

Исключения обложек и другие функции мета-метода могут быть записаны как "ортогональные" аспекты того, что делают ваши методы обслуживания.

Некоторая общая информация об АОП: АОП в Википедии

И потенциальное решение с контейнером: АОП с Виндзорским замком

Ответ 3

Ниже приведен пример использования дерева выражений:

public T HandleServiceCall<T>(Expression<Func<T>> serviceMethod)
{
    try
    {
        return serviceMethod();
    }
    finally
    {
        var serviceMethodInfo = ((MethodCallExpression)serviceMethod.Body).Method;
        Console.WriteLine("The '{0}' service method was called", serviceMethodInfo.Name);
    }
}

Обратите внимание, что в этом примере предполагается, что выражение serviceMethod всегда содержит вызов метода.

Связанные ресурсы:

Ответ 4

Да, я считаю, что ваш код переваривается.

Что касается упаковки вашего кода для общего безопасного использования прокси-серверов, посмотрите здесь для приятной реализации. Использовать его легко:

using (var client = new Proxy().Wrap()) {
 client.BaseObject.SomeMethod();
}

Теперь вам также нужно получить имя метода - для этого просто используйте Environment.StackTrace. Вам нужно добавить ходьбу в стек в Marc Gravell Wrap.