С#: Элегантный способ обертывания вызовов метода
Извинения за довольно неоднозначный заголовок, но то, что я пытаюсь достичь, вероятно, лучше указано в коде.
У меня есть 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
.