在上面一篇文章《》中,我提到最近一直为一个项目进行Code Review的工作,从中发现了一些问题,同时也有了一些想法。上次谈到如何正确编写服务MVP规范的程序,这次我们来关注一个我们每天都会面对的问题:异常处理。
一、异常处理不简单
个人觉得,异常处理对于程序员来说,尤其是对于那些初级.NET程序员来说,是最为熟悉的同时也是最难掌握的。说它熟悉,因为仅仅就是Try/Catch而已。说它难以掌握,很多开发人员却说不清楚Try/Catch应该置于何处?什么情况下需要对异常进行日志记录?什么情况下需要对异常进行封装?什么情况下需要对异常进行替换?对于捕获的异常,在什么情况下需要将其再次抛出?什么情况下则不需要。总之,异常处理没有我们想象的那么简单。
无论对于何种类型的应用,异常处理都是必不可少的。合理的异常处理应该是场景驱动的,在不同的场景下,采用的异常处理策略往往是不同的。异常处理的策略应该是可配置的,因为应用程序出现怎样的异常往往是不可预测的,现有异常策略的不足往往需要在真正出现某种异常的时候才会体现出来,所以我们需要一种动态可配置的异常处理策略维护方式。目前有一些开源的异常处理框架提供了这种可配置的、场景驱动的异常处理方式,EnterLib的Exception Handling Application Block就是一个不错的选择。
二、异常处理对于最终的开发人员是透明的
“异常处理对于最终的开发人员是透明的”,可能这句话说得有点过头。但是,就我个人的项目经验来讲,这是一种理想的状态。由于异常策略是一般是通过配置动态配置的,不需要反映在代码上面。如果能够通过框架的方式提供异常处理的实现,使开发人员无需编写任何异常处理的代码,只需要关注业务流程的实现就可以了,这不仅能够提高开发的效率,也能够提高系统的可维护性。
我们目前的项目是一个典型的分布式应用,所有的业务流程的处理和数据访问都实现在服务端,最终以WCF服务的形式暴露给客户端(Smart Client)和第三方应用。所有客户端和服务端从逻辑上具有相应的层次划分,但是异常处理仅仅实现在两个地方,一个地方是WCF服务本身,另一个实现UI层。忘了说明一点,我们项目直接将EnterLib的Exception Handling Application Block作为我们的异常处理框架。对于服务端的异常处理来说,我们通过WCF与EHAB的集成来实现的(《》),所以不需要开发人员添加任何一句Try/Catch代码。但是客户端来说,对于某个控件的事件来说,由于UI本身就是处于整个调用栈的最顶层,很难通过基于AOP的拦截机制来实现对异常处理的动态注入,所以客户端会出现非常类似于下面代码所示的Try/Catch。
1: private void buttonCalculate_Click(object sender, EventArgs e)
2: {
3: try
4: {
5: //
6: }
7: catch (Exception ex)
8: {
9: if (ExceptionPolicy.HandleException(ex, "policyName"))
10: {
11: throw;
12: }
13: }
14:
15: }
三、通过编写公共方法的形式实现代码的重用
1: public void Invoke(Action action)
2: {
3: try
4: {
5: action();
6: }
7: catch (Exception ex)
8: {
9: if (ExceptionPolicy.HandleException(ex, "data access policy"))
10: {
11: throw;
12: }
13: }
14: }
在调用的时候,只需要将相应的操作以Action类型的Delegate的形式传入Invoke方法即可。但是这样,也会在所有控件处理事件中出现重复的Invoke调用,虽然重复的代码行数减少了,但是还是会出现大规模的重复。接下里我来介绍另一种解决方法。
四、对EventHandler进行封装
认真分析上面的需求,我们的根本目的就是让执行事件处理程序的时候在外面人为地套一个Try/Catch,并对捕获的异常进行相应的处理。从这个意义上讲,如果我们能够对EventHandler或者ExventHandler<TEventArgs>进行相应的封装,就能实现我们需要的目的。
可能我这样说,你不会太明白,我们还是通过代码来说话好了。在下面我创建了一个用于封装EventHandler对象的EventHandlerWrapper类型。我们知道EventHandler是一个Delegate,而Delegate由两部分组成:表示操作本身的MethodInfo和操作执行的目标对象,分别通过属性Method和Target表示。在执行EventHandler的时候,就是通过反射的方式调用MethodInfo的Invoke方法,并将目标对象和相应的参数传入该方法而已。
1: using System;
2: using System.Diagnostics;
3: using System.Reflection;
4: using System.Text;
5: using System.Windows.Forms;
6: namespace ProgramingWithoutTryCatch
7: {
8: public class EventHandlerWrapper
9: {
10: public object Target
11: { get; private set; }
12:
13: public MethodInfo Method
14: { get; private set; }
15:
16: public EventHandler Hander
17: { get; private set; }
18:
19: public EventHandlerWrapper(EventHandler eventHandler)
20: {
21: if (null == eventHandler)
22: {
23: throw new ArgumentNullException("eventHandler");
24: }
25:
26: this.Target = eventHandler.Target;
27: this.Method = eventHandler.Method;
28: this.Hander += Invoke;
29: }
30:
31: public static implicit operator EventHandler (EventHandlerWrapper eventHandlerWrapper)
32: {
33: return eventHandlerWrapper.Hander;
34: }
35:
36: private void Invoke(object sender, EventArgs args)
37: {
38: try
39: {
40: this.Method.Invoke(this.Target, new object[] { sender, args });
41: }
42: catch (TargetInvocationException ex)
43: {
44: StringBuilder message = new StringBuilder();
45: message.AppendLine(string.Format("Message: {0}", ex.InnerException.Message));
46: message.AppendLine(string.Format("Exception Type: {0}", ex.InnerException.GetType().AssemblyQualifiedName));
47: message.AppendLine(string.Format("Stack Trace: {0}", ex.InnerException.StackTrace));
48: EventLog.WriteEntry("Application", message.ToString());
49: MessageBox.Show(ex.InnerException.Message + Environment.NewLine + "For detailed information, please view event log", string.Empty, MessageBoxButtons.OK, MessageBoxIcon.Error);
50: }
51: }
52: }
53: }
EventHandlerWrapper通过EventHandler对象创建,并将EventHandler的Target和Method赋值给EventHandlerWrapper的同名属性。此外,EventHandlerWrapper得Invoke方法中,将对Method的调用放在一个Try/Catch中,并对捕获的异常进行简单的处理:记录到EventLog中在通过MessageBox将相关异常信息显示出来。而EventHandlerWrapper的Handler属性就是对该Invoke方法的直接反映。最后定义了一个隐式类型转换将EventHandlerWrapper直接转换成EventHandler。转化后返回的就是反映Invoke方法的Handler属性。为了演示,我写了一个简单的计算器的应用。该应用运行后的界面如右图所示,这是一个进行简单除法运算的计算器。 下面是相关的代码:
1: using System;
2: using System.Windows.Forms;
3: namespace ProgramingWithoutTryCatch
4: {
5: public partial class Form1 : Form
6: {
7: public Form1()
8: {
9: InitializeComponent();
10: this.buttonCalculate.Click += new EventHandlerWrapper(buttonCalculate_Click);
11: }
12:
13: private void buttonCalculate_Click(object sender, EventArgs e)
14: {
15: int op1 = int.Parse(this.textBoxOp1.Text);
16: int op2 = int.Parse(this.textBoxOp2.Text);
17: int result = op1 / op2;
18: this.textBoxResult.Text = result.ToString();
19: }
20: }
21: }
代码非常简单,需要注意的是在对Button的Click事件进行注册的时候,我们直接使用的时我们上面创建的EventHandlerWrapper,这和真正进行事件注册的方式几乎一致。当你输入非数字或者被除数设置为的时候,会抛出异常,异常的相关信息会直接写入EventLog,并将异常消息通过MessageBox显示出来,如下图所示:
五、通过EventHandlerWrapper的写法实现其他的功能
EventHandlerWrapper实际上为了展示了对EventHandler进行封装的方式,异常处理并非其独有的应用场景。如果你看过我的文章《事件 (Event),绝大多数内存泄漏(Memory Leak)的元凶()()》,你会发现我通过相同的方式解决了事件注册导致的内存泄露的问题。在这里我在介绍另外一种有趣的应用。
在进行Windows Forms开发中,相信你会经常要求实现这样的功能:如果点击某个按钮后,需要较长的反映时间,需要在点击之后将Form的光标设置成沙漏的形状(Wait Cursor),当整个处理结束后再将其回复。我们可以对EventHandlerWrapper的Invoke方法略加修改就能够实现这个功能:
1: private void Invoke(object sender, EventArgs args)
2: {
3: if(null != Form.ActiveForm)
4: {
5: Form.ActiveForm.Cursor = Cursors.WaitCursor;
6: }
7: try
8: {
9: this.Method.Invoke(this.Target, new object[] { sender, args });
10: }
11: finally
12: {
13: if (null != Form.ActiveForm)
14: {
15: Form.ActiveForm.Cursor = Cursors.Default;
16: }
17: }
18: }
下一篇:如何在EHAB(EntLib)中定义”细粒度”异常策略?
-
在C#中如何把数据表中的图片绑定到WinForms图像列表
介绍 这个技巧将帮助您获取图像,并将其填充到 ImageList中 并显示图像逐一 PictureBox中 使用C#在Windows窗体。 背景 存储图像到SQL Server 2008 R2的。 使用代码 创建使用C#在winform一个新项目。 添加新的Winform。 现在添加一个 图片框 , 图像列表 ,
-
如何测试客户端工作中的Web API
在这篇文章中,我们将看到我们如何测试我们的API与一个叫WebApiTestClient包的帮助。 大家都知道,如果你创建一个示例API项目,你会得到一个地区文件夹HelpPage。 这是添加XML描述到每个控制器和行动,我们使用[&#8230;] 在这篇文章中,我们将看到我们如何
-
MongoDB的3.2,C#MongoClient 2.x中,TextResearch:如何应对?
对于那些谁在MongoDB中3.2文本研究与新的C#驱动程序处理,这里有一些建议。 介绍 你曾经尝试使用文本研究与MongoDB的C#3.2驱动程序? 那么我们可以说,它不是那么容易,因为它的声音......这里有一些建议。 构建文本索引 首先,所有的文本研究,你 必须 有
-
如何使用实体框架6在ASP.NET MVC 5 开发Web应用程序项目
介绍 一个Visual Studio 2013项目,显示了如何使用实体框架6在ASP.NET MVC 5 Web应用程序项目,使用代码首先开发方法。 使用EF 5和4 MVC在Visual Studio 2012中的项目以前的版本是可供下载。 的代码说明了以下主题: 创建使用数据注解属性的数据库映射数据模
-
ASP.NET MVC6开发中如何实现联系我们页面
介绍 这是一个关于 ASP.NET MVC 6(ASP.NET 1.0的核心)实施联系我们页面后。 起初,我创建了模型的联系表,为创建的视图,然后所有的逻辑的东西是在控制器完成。 这是使用MVC模式(模型视图和控制器模式)和 jQuery 用于客户端验证实现的。 背景 这是工作和
- 安卓源代码
- 网页下载
- asp源码下载
- 免费企业网站
- 企业网页设计模板
- html5网站
- PHP源码
- 网站源码下载
- 网页在线客服代码
- 建站代码
- 飞天侠淘宝客
- css模板
- .net源码
- 模板下载
- 导航条代码
- 学生个人网页制作
- 电脑报电子版
- flash动画素材网
- asp网站
- 电影网站程序
- 安卓源码
- 帝国cms
- discuz下载
- 免费论坛
- 网页模板
- 帝国模板
- 办公管理系统
- 源码网站
- 源码下载
- 网页制作模板
- app模版
- 免费源码
- 免费个人网站模板
- 源代码 在线
- 网页 模板
- 源码分享
- 个人网站设计模板
- php论坛
- 源码
- 商城源码
- 帝国cms模板
- php 框架
- 即时通讯
- 免费网站模板
- 安卓源码下载
- 免费代码
- php网站
- 网站模版下载
- 源代码 下载
- 登录界面模板