如何在方法调用周围添加常用功能?

发布于 2024-10-08 04:47:42 字数 2356 浏览 1 评论 0原文

有问题的代码是由机器人(CodeSmith)编写的,维护起来很痛苦。它看起来有点类似于:

public AddressProgramTemplate GetById(System.Int32 _id) {
    try {
        return Service.GetById(_id);
    } catch (FaultException<ErrorInfo> ex) {
        throw new ProxyServerBusinessException(ex.Detail);
    } catch (FaultException) {
        throw new ProxyServerBusinessException(null);
    } catch (EndpointNotFoundException ex) {
        throw new ProxyServerTechnicalException<EndpointNotFoundException>(ex);
    } catch (CommunicationObjectFaultedException ex) {
        throw new ProxyServerTechnicalException<CommunicationObjectFaultedException>(ex);
    } catch (CommunicationException ex) {
        throw new ProxyServerTechnicalException<CommunicationException>(ex);
    } catch (ObjectDisposedException ex) {
        throw new ProxyServerTechnicalException<ObjectDisposedException>(ex);
    } catch (TimeoutException ex) {
        throw new ProxyServerTechnicalException<TimeoutException>(ex);
    }
}

正如您可以猜到的,它是一个客户端 WCF 代理代码,并且对于每个服务方法(并且有很多)都会重复所有这些行。对机器人有利的事情对我来说却是一种悲哀,所以我开始重构它。首先,异常逻辑和处理被委托给 Microsoft 企业库,通用代码迁移到基类:

public TResult WrapServiceMethod<TResult>(Func<TResult> serviceMethod) {
    TResult result = default(TResult);
    try {
        result = serviceMethod();
    } catch (Exception ex) {
        bool rethrow = ExceptionManager.HandleException(ex, ExceptionPolicyNames.ClientRequestPolicy);
        if (rethrow) throw;
    }
    return result;
}

到目前为止一切顺利,丑陋的 try/catch 堆变成了简洁的一句台词:

return WrapServiceMethod<AddressProgramTemplate>(() => Service.GetById(_id));

一点点努力和 void 方法也被覆盖。当服务调用使用 out 参数时,就会出现问题:

public void GetPeriod(AddressProgram program, out DateTime startDate, out DateTime endDate){
    WrapServiceMethod(() => Service.GetPeriod(program, out startDate, out endDate));
}

导致“无法在匿名方法、lambda 表达式或查询表达式中使用 ref 或 out 参数 'endDate'”,并且我了解原因

我理想中希望拥有的是定义自定义运算符块的能力,例如 while() 或 using(),这样我就可以编写

wrapexception { ... }

并从此幸福地生活,但我认为这个技巧在 .NET 中是不可能的。假设重写所有不带 out 参数的服务方法是最后的手段,我还有其他选择吗?

The code in question is written by robot (CodeSmith) and it is a pain to maintain. It looks somewhat similar to:

public AddressProgramTemplate GetById(System.Int32 _id) {
    try {
        return Service.GetById(_id);
    } catch (FaultException<ErrorInfo> ex) {
        throw new ProxyServerBusinessException(ex.Detail);
    } catch (FaultException) {
        throw new ProxyServerBusinessException(null);
    } catch (EndpointNotFoundException ex) {
        throw new ProxyServerTechnicalException<EndpointNotFoundException>(ex);
    } catch (CommunicationObjectFaultedException ex) {
        throw new ProxyServerTechnicalException<CommunicationObjectFaultedException>(ex);
    } catch (CommunicationException ex) {
        throw new ProxyServerTechnicalException<CommunicationException>(ex);
    } catch (ObjectDisposedException ex) {
        throw new ProxyServerTechnicalException<ObjectDisposedException>(ex);
    } catch (TimeoutException ex) {
        throw new ProxyServerTechnicalException<TimeoutException>(ex);
    }
}

As you can guess, it's a client-side WCF proxy code and all these lines are repeated for every service method there is (and there are many). What's good for robot is a sorrow for me, so I started to refactor it. First of all, the exception logic and handling is delegated to Microsoft Enterprise Library and common code migrated to base class:

public TResult WrapServiceMethod<TResult>(Func<TResult> serviceMethod) {
    TResult result = default(TResult);
    try {
        result = serviceMethod();
    } catch (Exception ex) {
        bool rethrow = ExceptionManager.HandleException(ex, ExceptionPolicyNames.ClientRequestPolicy);
        if (rethrow) throw;
    }
    return result;
}

So far so good, the ugly try/catch pile becomes a neat one-liner:

return WrapServiceMethod<AddressProgramTemplate>(() => Service.GetById(_id));

A little effort and void methods covered as well. The problem comes when service calls use out parameters:

public void GetPeriod(AddressProgram program, out DateTime startDate, out DateTime endDate){
    WrapServiceMethod(() => Service.GetPeriod(program, out startDate, out endDate));
}

Results in "Cannot use ref or out parameter 'endDate' inside an anonymous method, lambda expression, or query expression" and I understand why.

What I would ideally like to have is an ability to define custom operator blocks such as while() or using(), so I could write

wrapexception { ... }

and live happily ever after, but I don't think this trick is possible with .NET. Assuming that rewriting all service methods without out parameters is a last resort, do I have any other options?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一抹微笑 2024-10-15 04:47:42

听起来您正在寻找面向方面的编程库,例如 PostSharp。

您可以创建异常处理程序,这些异常处理程序会根据您指定的规则在编译后插入到 IL 中,这些处理程序可以执行诸如捕获异常、日志记录、跟踪等操作

。这样做的好处是您只需编写一次切面,即可将其应用于多个方法。这些方法不会混杂与当前特定任务无关的代码,因为方面会处理诸如异常处理之类的横切问题。

查看示例 http://www.sharpcrafters.com/solutions/monitoring#异常监控显示如何处理异常。

Sounds like you're after an aspect oriented programming library, such as PostSharp.

You can create Exception handlers that are inserted into your IL after compilation based on rules you specify which can do things such as catching exceptions, logging, tracing etc.

The benefit of this is you write the aspect once, and apply it to multiple methods. These methods aren't cluttered with code unrelated to the specific task at hand, as the cross cutting concerns such as exception handling are taken care of by the aspect.

Take a look at the example at http://www.sharpcrafters.com/solutions/monitoring#exception-monitoring which shows how to handle exceptions.

隔纱相望 2024-10-15 04:47:42

更改 out 签名(因此必须更改所有调用代码,我确定您想避免)的替代方法可以执行以下操作:

public void GetPeriod(AddressProgram program, out DateTime startDate, out DateTime endDate)
{
    var outs = WrapServiceMethod(() => 
        {
            DateTime sd;
            DateTime ed;
            Service.GetPeriod(program, out sd, out ed));
            return new {sd, ed};
        }
    startDate = outs.sd;
    endDate = outs.ed;
}

An alternative to changing the out signatures (and thus having to change all calling code, which I'm sure you want to avoid) you could do something like this:

public void GetPeriod(AddressProgram program, out DateTime startDate, out DateTime endDate)
{
    var outs = WrapServiceMethod(() => 
        {
            DateTime sd;
            DateTime ed;
            Service.GetPeriod(program, out sd, out ed));
            return new {sd, ed};
        }
    startDate = outs.sd;
    endDate = outs.ed;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文