委托与事件
“委托”是一种指向一个类的静态方法,或者实例方法的数据结构,委托类似于 C++ 函数指针,但它是类型安全的。委托允许将方法作为参数进行传递,一旦为委托分配了方法,委托就将与该方法具有完全相同的行为。
委托主要用在两个方面:其一是CallBack(回调)机制;其二是事件处理机制。什么是回调,在上一篇文章中介绍过了。关于事件处理,在本文的后面,也将进行介绍。
委托可以链接在一起;例如,可以对一个事件调用多个方法。
委托实现回调的时候,可以直接传方法名,也可以传委托实例,如下:
using System; using System.Collections.Generic; using System.Text; namespace ConsoleApplication1 { class Program { public delegate void DelCout(string str); static void Main(string[] args) { //直接传方法,在编译的时候,还是会帮我们new,这就是一个语法糖。 DelMethod(Cout,"直接传方法"); DelMethod(new DelCout(Cout),"传委托实例"); Console.Read(); } private static void DelMethod(DelCout delCout,string str) { delCout(str); } private static void Cout(string str) { Console.WriteLine(str); } } }
关于委托的两个主要作用,可能你明白事件要通过委托来实现,这是C#内部定义成这样的,但不清楚为什么回调机制也要用委托。
在C\C++中,实现回调是通过函数指针来实现的,函数指针其实就是个内存地址,由于该地址不会携带任何其他信息,如函数期望的参数个数、参数类型、返回值类型等,所以这时的回调函数是非类型安全的.C#为回调函数提供了称为委托的机制,其能提供所期望的参数个数、参数类型、返回值类型等信息,因此委托是类型安全的.
二、事件
先给个例子:(代码来源:http://www.cnblogs.com/donghaiyiyu/archive/2007/07/29/828738.html)
using System; using System.Collections.Generic; using System.Text; namespace EventDemo { public delegate void SalaryCompute(); //声明一个代理类 public class Employee { public event SalaryCompute OnSalaryCompute; //定义事件,将其与代理绑定 public virtual void FireEvent() //触发事件的方法 { if (OnSalaryCompute != null) { OnSalaryCompute(); //触发事件 } } } public class HumanResource { public void SalaryHandler() //事件处理函数 { Console.WriteLine("Salary"); //只是打印一行字而已 } public static void Main() { Employee ep = new Employee(); HumanResource hr = new HumanResource(); ep.OnSalaryCompute+=new SalaryCompute(hr.SalaryHandler); //注册 ep.FireEvent(); //触发事件 Console.Read(); } } }
其实,从上面的代码看来,事件就是回调。所触发的事件,就是回调方法。
“事件”是指当对象发生某些事情时,向其它对象提供通知的一种方法。在C#中,事件是通过delegate来实现的。
事件有两个角色:一是事件发送方,一是事件接收方。事件发送方是指触发事件的对象,事件接收方是指注册事件发生时被通知的对象。
这里,我想重点说下事件伪造事件的解决方法。下面先看下什么是伪造事件。
在一个类中我们定义一个委托,把它做公有类型。如下:
public delegate void DelDbClick(); public DelDbClick delDbClick;
那么,外部的一个类,可以做一个方法,把这个委托设置成NULL(清除监听),也可以主动触发这个委托所关联的事件(假冒事件)。这种时候就可以造成“伪造事件”。
这个时候,我们可以用事件来对它进行包装,以消除上面的这两种情况。
public delegate void DelDbClick(); private DelDbClick delDbClick; public event DelDbClick AddEvent { add { delDbClick += value; } remove { delDbClick -= value; } }
像这样,把委托定义成私有的,然后定义一个这个委托类型的事件来把它向外开放。那么外部就既不能直接把委托设置成NULL,也不能直接调用委托了。
当然,我们可以直接写一个方法来做这种包装,就象用属性来包装私有字段一样,但这样定起来更简单。
这本来不应该单独写,但这个问题,被问得太多次了~~
首先应该明白,委托得一种类型(书上也说是一种数据结构),它与类平级,而事件是与方法属性同级别的,事件是用委托来实现的。有人说事件是一种特殊的委托,我觉得这样理解也可以。通过上面的例子,我们也知道,事件是对委托封装,方便了事件注册(多播委托),还可以消除误操作(事件伪装)。
这是我一次面试的时候,被问到的。当时,面试官问我对于多线程了解多少。我确实不清楚,但也不能一点也不说。
说到时交叉通信要用委托时,面试官问:为什么要用委托,一下子懵了。
我现在的理解是:交叉通信的时候,用到了一个名为“invok”的方法,而这个方法中要求提供一个委托过去。其实,这个地方就是一个回调。那么,为什么用委托就清楚了。因为在C#中不能向C/C++一样,直接把方法作为参数传递。因为C/C++中传递方法名是一个指针,C++中的指针不通过MSIL而是直接和内存打交道,这便是指针不安全的原因所在。而委托则不同。C#中的委托不与内存打交道,而是把这一工作交给CLR去完成成。CLR无法阻止将不安全的代码调用到本机(非托管)代码中或执行恶意操作。然而当代码的类型安全时,CLR的安全性强制机制将确保代码不会访问本机代码,除非它有访问本机代码的权限。此外,委托作为参数进行传递的时候,传递的信息包括参数个数,参数类型,返回值类型等。因此委托是类型安全 。
我觉得这也就是为什么要用委托的原因了。