一晃已经工作好几个月了,一直想没搞明白委托和事件到底有什么妙用。记得刚开始学习C#时,入门也就一个星期的时间,只是匆匆过了一遍语法。学到委托和事件时,就看了博客园张子阳的委托和事件,认识就停留在网上猫大叫的题目上,一直没用过也就没有体会到什么妙处。后来开始找工作了,再看那篇文章还是似懂非懂,不用又忘了。也许领悟力欠缺了点,直到最近遇到实际应用才稍微有些明朗。
当时对于委托,我不明白的地方主要有:1委托是一种类型(现在想想,这就是规定);2为什么要用委托(因为当时看到的那些例子明明就可以不用委托,一样简洁);3委托的优势(或者说妙用);4还不明白谁来调用委托,委托到底定义在哪,只知道按着张子阳那个例子把猫大叫的例子写好,两个都是控制台程序,都是在Main函数中绑定操作的。
第一次遇到的问题是Form窗体间传值问题:一个主窗体FormMain,显示信息,然后我想弹出信息设置窗体,在弹出的FormNew窗体中修改信息值保存到FormMain中。这个例子在WinForm中这个应用还是很常见的。
因为第一次遇到,想当然的就new一个FormMain,然后将值改变,一运行发现重新弹出一个FormMain窗体,再看程序原来是没有获得原来那个FormMain对象,于是乎就传了个This给新窗体。由于是第一次遇到还有些沾沾自喜,发现自己能够在两个页面之间任意传值了。后来晚上回去随意查了查页面间传值,一查发现我有收获了,原来别人早就遇到这种情况了,而且还是个问题,我的方法太浪费了,不值得,各种留言中总共有三个版本,一个是我这种方法,一个是主窗体静态控件,还有一个是委托和事件。静态控件很容易解决,当时就不明白怎么用委托实现,于是又看张子阳的事件与委托,一个晚上才弄明白,会了也就感觉简单了。
先上代码:
FormMain窗体:一个Lable,一个TextBox,一个Button,点击Button弹出FormNew窗体
using System; using System.Windows.Forms; namespace DelegateDemo20111119 { public delegate void ChangeDelegate(string stringValue); public partial class FormMain : Form { public FormMain() { InitializeComponent(); } private void btn_NewForm_Click(object sender, EventArgs e) { FormNew frmNew = new FormNew(); frmNew.changeValue = ChangeValue; frmNew.ShowDialog(); } private void ChangeValue(string textValue) { this.tb_Value.Text = textValue; } } }
FormNew窗体:一个Lable,一个TextBox,一个Button,点击Button将TextBox中的内容传到FormMain窗体中。
using System; using System.Windows.Forms; namespace DelegateDemo20111119 { public partial class FormNew : Form { public ChangeDelegate changeValue; public FormNew() { InitializeComponent(); } private void btn_OK_Click(object sender, EventArgs e) { if (changeValue != null) changeValue(tb_NewValue.Text); } } }
这里要注意的:委托定义的位置是在命名空间内,在同一个命名空间内都可以用这个委托类型,不理解的可以想象在命名空间内定义结构或者枚举类型,地位是等价的。
其实上面的例子还是不能够明确表明委托是什么,在网上有些人说可以用指针来理解委托,委托变量绑定方法时直接获得了方法在内存中的地址,因此委托无需通过类来查找方法的地址。而其他类,如果没有操作该类的权限(即无法获得该类的内存地址,也就无法通过类来查找方法的内存地址),也就无法操作该类的方法。有了委托,我可以只关心需要操作的那个方法,将他绑定给委托变量即可。上面例子FormNew类其实并不关心FormMain,只关心的是将FormMain上的TextBox文本框的值更新一下,也就是只关心ChangeValue这个函数。frmNew.changeValue = ChangeValue将ChangValue方法绑定到委托,要更新时就直接调用委托,该委托保存的是ChangeValue方法的内存地址,也就直接操作的ChangeValue方法。
后来查了委托的妙用,找了好久好久,才在优快云论坛一个留言中看到有人说底层API应用中常常用到,比如网卡、声卡等,我们不能操作网卡声卡,但对外有操作的方法,只要传入参数即可。我想到了以前学的一钩子程序用到了API,就跟这个类似,当时只知道依样画葫芦传入一方法名,现在串起来理解,容易多了。于是模仿着把这个例子改了改,在命名空间下新建了一个DelegateHelper类,有一静态委托变量changeDelegate。在FormMain实例化时就将ChangeValue方法绑定到changeDelegate委托上,以后别的窗体要改变FormMain的状态,只需调用changeDelegate即可,而不需管能不能直接操作FormMain,只要委托不为空就说明已经绑定了,就可直接改变FormMain的相关信息。
DelegateHelper类:
using System; namespace DelegateDemo20111119 { public class DelegateHelper { public static ChangeDelegate changeDelegate; } }
FormMain类:
using System; using System.Windows.Forms; namespace DelegateDemo20111119 { public delegate void ChangeDelegate(string stringValue); public partial class FormMain : Form { public FormMain() { InitializeComponent(); DelegateHelper.changeDelegate = ChangeValue; } private void btn_NewForm_Click(object sender, EventArgs e) { FormNew frmNew = new FormNew(); frmNew.ShowDialog(); } private void ChangeValue(string textValue) { this.tb_Value.Text = textValue; } } }
FormNew类:
using System; using System.Windows.Forms; namespace DelegateDemo20111119 { public partial class FormNew : Form { public FormNew() { InitializeComponent(); } private void btn_OK_Click(object sender, EventArgs e) { if (DelegateHelper.changeDelegate != null) DelegateHelper.changeDelegate(tb_NewValue.Text); } } }
第二次遇到纯粹是巧合,我那天重构代码时,想解决消除许多if else的问题,搜到博客园谢平的这篇文章,看到下边讲了个表驱动处理异常,而我一直也不会全局处理异常,只会局部的try catch finally,顿时大感兴趣。于是我就先原封不动的照搬了,类的结构参照的他的(还是说抄吧),参考的思想我就不重复讲思想了。一般自定义的异常继承自ApplicationException,且在低层手动抛出,在高层捕获。
先上异常结构的代码:
自定义异常基类: MyException.cs
using System; namespace ExceptionHelper { public class MyException:ApplicationException { private string className = "MyException"; private string message; public virtual short Index { get { return 0; } } public virtual void TreateException(Exception ex) { } public MyException() : base() { } public MyException(string message):base(message) { this.message = className+ message; } public MyException(string message, Exception innerException) : base(message, innerException) { } } }
自定义异常类一: MyFirstException.cs
using System; namespace ExceptionHelper { public class MyFirstException:MyException { private string className = "MyFirstException"; private string message; public override short Index { get { return 1; } } public override void TreateException(Exception ex) { } public MyFirstException() : base() { } public MyFirstException(string message):base(message) { this.message = className+message; Console.WriteLine(this.message); } public MyFirstException(string message, Exception innerException) : base(message, innerException) { } } }
我只讲讲我添加的部分,类的结构是有了,我就想如何对这些进行统一管理,让代码改变时无需牵一发而动全身。
根据他的思想先添加一个管理类,为了控制只有一个异常管理字典,我考虑用静态构造函数实现,异常管理字典的值是委托类型的,如下所示:
using System; using System.Collections.Generic; namespace ExceptionHelper { public delegate void ForException(Exception ex); public class ExceptionManage { public static Dictionary<short, ForException> dictException; static ExceptionManage() { dictException = new Dictionary<short, ForException>(); dictException.Add(0, new MyException().TreateException); dictException.Add(1, new MyFirstException().TreateException); } } }
在业务高层时捕获异常
try { //这里写需要执行的语句 } catch (MyException ex) { MyException myException = ex as MyException; short index = myException.Index; ExceptionManage.dictException[index].Invoke(ex); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); }
至此,他的思想全部完成。问题在于现在我的每个高层业务中都有这样一个程序段,虽统一,但还是存在重复,代码还需重构。于是我想起了委托,将函数作为参数,那样只需调用一个函数,参数就是原来的函数名。把高层函数中的语句都提取出成函数,不现实,那样太假了(自我感觉,感觉那样有点重复),相当于把所有的高层全部重新写了一遍。其实会出异常的也就那么几句(甚至只有一句),因为好多都只是一串语句中的一句,可以把那几句抽取出来。然而这些从中间抽取语句可不容易,有的功能是改变状态,有的只是执行,还有的需要返回值。于是我根据业务写了若干委托(因为绑定委托时参数都要相对应),为了少写几个函数,我还稍微调整了几个函数参数的顺序,最可恨的就是有返回值的,还要传引用。
public static void TryExcute(DelegateOne TryMethod)
{
try
{
TryMethod();
}
catch (MyException ex)
{
MyException myException = ex as MyException;
short index = myException.Index;
ExceptionManage.dictException[index].Invoke(ex);
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
}
//无参,有一个string类型的返回值
public static void TryExcute(DelegateRefOne TryMethod,ref string arg)
{
try
{
arg =TryMethod();
}
catch (MyException ex)
{
MyException myException = ex as MyException;
short index = myException.Index;
ExceptionManage.dictException[index].Invoke(ex);
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
}
再次重构时,我就在想,如果高层添加了业务出现了新种类的函数,还得在这边添加,能不能用泛型呢,想起不久前看到的VS自带有Action,MSDN上一查,把所有的不带参数的都统一了,当时也就没有再考虑有参数的了,晚上回去之后我就想,为什么微软不提供一个有返回值的泛型委托呢,其实是我没搜,我一搜果然有,Func可以解决。于是管理类改进为如下。
using System; using System.Collections.Generic; namespace ExceptionHelper { public delegate void ForException(Exception ex); public class ExceptionManage { public static Dictionary<short, ForException> dictException; static ExceptionManage() { dictException = new Dictionary<short, ForException>(); dictException.Add(0, new MyException().TreateException); dictException.Add(1, new MyFirstException().TreateException); } public static void TryExcute(Action TryMethod) { try { TryMethod(); } catch (MyException ex) { MyException myException = ex as MyException; short index = myException.Index; ExceptionManage.dictException[index].Invoke(ex); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } public static void TryExcute<T>(Action<T> TryMethod,T arg) { try { TryMethod(arg); } catch (MyException ex) { MyException myException = ex as MyException; short index = myException.Index; ExceptionManage.dictException[index].Invoke(ex); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } public static void TryExcute<S,V>(Action<S,V> TryMethod, S arg1,V arg2) { try { TryMethod(arg1, arg2); } catch (MyException ex) { MyException myException = ex as MyException; short index = myException.Index; ExceptionManage.dictException[index].Invoke(ex); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } public static void TryExcute<R>(Func<R>TryMethod, R result) { try { result=TryMethod(); } catch (MyException ex) { MyException myException = ex as MyException; short index = myException.Index; ExceptionManage.dictException[index].Invoke(ex); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } public static void TryExcute<T, R>(Func<T, R> TryMethod, T arg, R result) { try { result = TryMethod(arg); } catch (MyException ex) { MyException myException = ex as MyException; short index = myException.Index; ExceptionManage.dictException[index].Invoke(ex); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } public static void TryExcute<S, T, R>(Func<S, T, R> TryMethod, S arg1, T arg2, R result) { try { result = TryMethod(arg1, arg2); } catch (MyException ex) { MyException myException = ex as MyException; short index = myException.Index; ExceptionManage.dictException[index].Invoke(ex); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } } }
注:1Action和Func都在System命名空间下。2泛型当有几个参数时要用不同的字母区分。3委托的 Invoke方法表示直接执行委托。4 异常的StackTrace表示详细的异常信息,比较有价值。5Func中R类型的参数表示返回值,用out实现的。
至此全部完成,只是个人的一些理解,希望能对初学者有些帮助,如果有什么错误,还望路过的大牛们不吝指教。转载请保留原文链接,点此源代码下载。