好久没写文章了,之前说要总结一下事件这个概念,今天趁着工作闲暇,谈谈我对C#事件事件的理解,如果有什么不对的地方,望各位大神指点。
真正的理解事件,还是要很好的理解委托,不然你永远不会明白很多教科书上的一句话:事件是一个特殊的委托,今天我们就来探寻一下:为什么说事件是一个特殊的委托,之前我谈论了委托的一些相关基础知识,其实还有一些关于委托更深层次的东西没有说,那么在今天谈事件这个知识之前还是继续从委托说起:
不知道有没有人了解C语言的函数指针这个概念,使用函数指针来创建回调函数,使用回调可以把函数回调给程序中的另一个函数。但函数指针只是简单地把地址指向另一个函数,并不能传递其他额外信息。而在.NET中,在大部分时间里都没有指针的身影,因为指针被封闭在内部函数当中。可是回调函数却依然存在,它是以委托的方式来完成的。委托可以被视为一个更高级的指针,它不仅仅能把地址指向另一个函数,而且还能传递参数,返回值等多个信息。系统还为委托对象自动生成了同步、异步的调用方式,开发人员使用BeginInvoke、EndInvoke 方法就可以抛开 Thread 而直接使用多线程调用 。其实这才是C#委托的本质由来,之前总结了那么多关于委托的概念,我始终没有从C语言的角度去解析委托,是因为一开始就吧上面的概念展示给初学者,其实是很不利于我们更好的,更深刻的去学习委托这个概念的。对于一个.net程序猿来说,适当的学习C语言是更有利于我们去理解C#高级的语法的。废话不多说,我们定义了一个名为MyDel委托,通过反射来遍历委托内部的属性和方法:
PropertyInfo[] pro = typeof(MyDel).GetProperties(); MethodInfo[] med = typeof(MyDel).GetMethods(); //MyDel a = new MyDel((name) => { return ""; }); Console.WriteLine("--------------------属性"); foreach (var item in pro) { Console.WriteLine(item.Name); } Console.WriteLine("--------------------方法"); foreach (var item in med) { Console.WriteLine(item.Name); }
得到如下结果:
我们还可以通过反射得到更多关于委托的元数据,这里就不一一列出了,所以我们可以看出委托其实也是一个类,它的内部也有对应的成员(属性,方法等),而且它派生自System.MulticastDelegate类。这是我从资料上截取的关于委托内部成员的介绍:
MulticastDelegate具有两个常用属性:Method、Target。其中Method 用于获取委托所表示的方法Target 用于获取当前调用的类实例。
MulticastDelegate有以下几个常用方法:
方法名称 | 说明 |
---|---|
Clone | 创建委托的浅表副本。 |
GetInvocationList | 按照调用顺序返回此多路广播委托的调用列表。 |
GetMethodImpl | 返回由当前的 MulticastDelegate 表示的静态方法。 |
GetObjectData | 用序列化该实例所需的所有数据填充 SerializationInfo 对象。 |
MemberwiseClone | 创建当前 Object 的浅表副本。 |
RemoveImpl | 调用列表中移除与指定委托相等的元素 |
多播委托:委托是可以同时赋值多个方法的,即委托对象可以绑定多个方法。当输入参数后,每个方法会按顺序进行迭代处理,并返回最后一个方法的计算结果。,如下:
public delegate int MyDel(int name); class Program { static int Add1(int a) { int b = 10 + a; Console.WriteLine(b); return b; } static int Add2(int a) { int b = 10 - a; Console.WriteLine(b); return b; } static void Main(string[] args) { MyDel add = new MyDel(Add1); add += new MyDel(Add2); Console.WriteLine(add(10)); } }
很显然多播委托有很大的缺陷,无论绑定多少个方法,该委托最终都只会返回最后一个方法的结果,不能实现它使用一对多的方式,可以让多个观察者同时关注同一个事物,并作出不同的响应。因此我们要对上面的代码做如下更改:
public delegate int Handler(int a); public class Manager { private Handler Handler; //加入观察者 public void Attach(Handler Handler1) { Handler += Handler1; } //删除观察者 public void Detach(Handler Handler1) { Handler -= Handler1; } //通过GetInvodationList方法获取多路广播委托列表,如果观察者数量大于0即执行方法 public void Execute(int basicNum) { if (Handler != null) if (Handler.GetInvocationList().Count() != 0) Handler(basicNum); } } class Program { static int Cul1(int a) { int b = 10 + a; Console.WriteLine(b); return b; } static int Cul2(int a) { int b = 10 - a; Console.WriteLine(b); return b; } static void Main(string[] args) { Manager manager = new Manager(); //加入Add1观察者 Handler HandlerA = new Handler(Cul1); manager.Attach(HandlerA); //加入Add2观察者 Handler HandlerB = new Handler(Cul2); manager.Attach(HandlerB); //同时加入10,分别进行计算 manager.Execute(10); Console.ReadKey(); } }
上面会在控制台返回20和0,上面的代码其写法的非常巧妙,而且也使用了设计模式中的观察者模式,如果你仔细研究了代码,你可能有个疑惑,我们之前为委托绑定多个方法时就只能返回最后一个方法的执行结果,为什么在此处就可以对应不同的方法返回不同的结果呢?我们在此处的委托绑定方法的方式与之前委托的绑定方式是有区别的,我们把上面观察者模式的代码简化,结果还是输出20和0:
public delegate int Handler(int a); class Program { static int Cul1(int a) { int b = 10 + a; Console.WriteLine(b); return b; } static int Cul2(int a) { int b = 10 - a; Console.WriteLine(b); return b; } static void Main(string[] args) { //Manager manager = new Manager(); //加入Add1观察者 Handler HandlerA = new Handler(Cul1); //加入Add2观察者 Handler HandlerB = new Handler(Cul2); Handler Handler; Handler = HandlerA; Handler += HandlerB; Handler(10); Console.ReadKey(); } }
对比我们最开始写的为委托绑定方法,我们是将两个方法绑定到同一个委托之中。而在此处,我们是将两个不同方法的分别赋值给了HandlerA和HandlerB两个委托,然后再将HandlerA和HandlerB依次绑定给了Handler,这样写与之前有什么区别呢?我们可以从指针的角度取理解它,最开始的第一种绑定方式将两个方法绑定到同一个委托之中:两个方法的指针是指向同一个委托(你可以理解为两个方法指向同一片内存空间),而无论绑定多少个方法,一个委托最终都只会返回最后一个方法的结果。而之后的第二种绑定方式在第一部的基础上加了一部,先将两个方法分别赋值给了HandlerA和HandlerB两个不同的委托,所以两个方法指向不同的委托(因为不是赋值给相同的委托,所以可以理解为两个方法指向不同的内存空间),然后再将绑定了两个不同的委托依次绑定给Handler委托,这样再次绑定的好处是可以对不同的观察者加以区分,因而可以对不同方法返回不同的结果。理解这一点非常重要,对理解之后的事件有很大的帮助。下面我们再将之前观察者模式的Handler定义到Main方法之外:
public delegate int Handler(int a); class Program { public static Handler EventHandler; static void Main(string[] args) { //Manager manager = new Manager(); //加入Add1观察者 Handler HandlerA = new Handler(Cul1); //加入Add2观察者 Handler HandlerB = new Handler(Cul2); EventHandler += HandlerA; EventHandler += HandlerB; EventHandler(10); Console.ReadKey(); } }
然后把Handler委托改成事件
public delegate int Handler(int a); class Program { public static event Handler EventHandler; static void Main(string[] args) { //Manager manager = new Manager(); //加入Add1观察者 Handler HandlerA = new Handler(Cul1); //加入Add2观察者 Handler HandlerB = new Handler(Cul2); EventHandler+= HandlerA; EventHandler+= HandlerB; EventHandler(10); Console.ReadKey(); } }
哈哈,仔细对比两段代码,你会发现下面除了代码在申明时多了一个event关键字之外,其他地方竟然完全一样,所以写到这里你就会明白大多数书上为什么说写到的事件一个特殊委托了吧!其实你可以理解为事件就一个委托的实例,它的作用在于再次绑定委托,以区分同一委托对应所绑定的不同的方法,实现对绑定了同一事件的不同的观察者方法做出不同的处理。今天就到此为止吧,下次继续来讲解委托与事件更多的作用与关系。