【问题标题】:C#: Elegant way to wrap method callsC#:包装方法调用的优雅方式
【发布时间】:2011-08-02 23:00:41
【问题描述】:

为这个相当模棱两可的标题道歉,但我想要实现的目标可能在代码中更好地说明。

我有一个 WCF 客户端。当我调用方法时,我想将每个调用包装在一些错误处理代码中。因此,我没有直接公开方法,而是在客户端类上创建了以下帮助函数:

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

而客户端代码是这样使用它的:

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

对 Ping 的调用很好地包含在一些尝试处理任何错误的逻辑中。

这很好用,只是我现在需要知道服务上实际调用了哪些方法。最初,我希望只使用表达式树检查Func&lt;IApplicationService, T&gt;,但并没有走得太远。

最后,我确定了一个装饰器模式:

    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")
            }
        }
    }

还有装饰器本身:

    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);
        }
    }

它真的很笨重,而且代码很多。 有没有更优雅的方式来做到这一点?

【问题讨论】:

  • 你在哪里创建装饰器?
  • 表达式树应该是要走的路。你能显示代码并告诉我们问题所在吗?
  • 听起来像是 PostSharp 的工作。
  • @smartcaveman:与HandleServiceMethod同父类中的私有类
  • @Daniel:这就是我首先尝试但在第一个障碍中失败的方法 - 将 Func 转换为 Expression

标签: c# decorator func


【解决方案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)
    {
        ...     
              }
}

【讨论】:

  • 这与@Enrico 对如何检查方法调用的描述相结合,效果很好。它还顺便解决了另一个问题,因为它可以防止客户端输入多个方法调用 - 这是一个很好的补充。
  • 我的最终解决方案是结合@Richard Friend 和@Enrico Campidoglio 给出的答案,我使用Enrico 的代码来识别被调用的方法。
【解决方案2】:

您是否考虑过使用面向方面的方法?这听起来正是您所需要的。

包装异常和其他“元方法”功能可以写成与您的 serviceMethods 的功能“正交”的方面。

关于 AOP 的一些一般信息:AOP in wikipedia

还有一个使用容器的潜在解决方案:AOP with Windsor Castle

【讨论】:

  • 我同意,这很合适。对我来说,感觉有点矫枉过正。有没有更简单的原生替代方案?
  • 一个更轻量级的版本可能是(上面建议的)PostSharp。不过我没用过。
【解决方案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 表达式始终包含方法调用。

相关资源:

【讨论】:

  • 只有一个方法调用?
  • 试图对此进行测试,但 Func 不包含 Body 的定义。
  • 需要将HandleServiceCall&lt;T&gt;方法的参数类型改为Expression&lt;Func&lt;T&gt;&gt;
  • 您传递给Expression&lt;Func&lt;T&gt;&gt; 参数的 lambda 表达式不能包含多个语句,否则您将收到编译器错误,因为它无法转换为表达式树(“带有语句体不能转换为表达式树").
  • 我已将您的答案与@Richard's 结合起来,并按照您的建议,使用 Expression 作为论据。您关于多个语句的第二点实际上对我有益,因为它限制客户端一次只能调用一个方法。
【解决方案4】:

是的,我认为您的代码煮过头了。

在包装您的代码以实现常见的安全处理代理方面,请查看here 以获得一个不错的实现。使用很简单:

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

现在您还需要访问方法名称 - 为此只需使用 Environment.StackTrace。您需要在 Marc Gravell 的 Wrap 中添加向上堆栈。

【讨论】:

  • 这种方法与我设计代码的方式略有不同。如果我要进行一些繁重的重构,我肯定会考虑这个。
猜你喜欢
  • 1970-01-01
  • 2018-04-01
  • 2015-03-22
  • 1970-01-01
  • 2020-05-05
  • 1970-01-01
  • 2013-11-14
  • 2018-01-12
  • 1970-01-01
相关资源
最近更新 更多