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: }