.NET是采用”委托事件模型”来处理事件的, 委托事件模型的特点是: 将事件的处理委托给独立的对象, 而不是事件源本身, 从而将使用者界面与程序逻辑分开. 整个”委托事件模型”由产生事件的对象(事件源)、事件参数对象及事件监听者对象之间的关系所组成.
产生事件的对象(事件源)会在事件产生时, 将与该事件相关的信息封装在一个称之为”事件参数对象”的对象中, 并将该对象传递给监听者对象, 监听者对象根据该事件参数对象内的信息决定适当的处理方式.
监听者要收到事件发生的通知, 就必须要在程序中向事件源注册, 当事件发生时, 事件源就会主动通知监听者对象, 监听者对象就可以根据产生事件的对象来决定处理事件的方法.监听者对象就是用来处理事件的对象, 监听者对象等候事件的发生并在事件发生时收到通知.
换句话讲, 事件是处理通知过程的对象, 也是.Net开发人员监视应用程序执行时出现的各种Windows消息的方式. 如果没有事件就必须监视WndProc捕获类似于WM_MOUSE_DOWN的消息, 而不是捕获按钮的鼠标Click事件.
可见, 在事件处理中, 委托非常重要. 要想理解事件, 必须知道委托是什么.
委托是方法的类型, 是具有相同返回值, 相同参数类型的一类方法的类型. 如何理解呢?
我们知道, 计算机只能顺序处理指令, 由CPU根据内部的指令寄存器(IP)逐条获取指令地址后执行指令. 我们在程序中定义变量、类以及方法等的代码, 在CPU看来只是一条条的地址, CPU通过指令寄存器依次读取并执行, 而程序中的变量的读取等操作其实就是值的拷贝和地址的传递.
既然委托代表方法的类型, 那么我来看一下方法. 方法也称为函数, 计算机要执行方法首先需要找到要执行的方法, 而这个操作是通过函数指针来完成的, 函数指针就是内存中存放该方法的内存地址. 组成方法的要素有返回值、方法名称及参数列表, CPU根据方法名(内存地址)找到方法后, 先将实参push到堆栈, 然后开始执行方法中的代码, 执行过程中需要参数时将会从堆栈中pop实参, 当方法执行完后将返回值push到堆栈, 此时我们用变量接收方法的返回值时, 将会从堆栈pop返回值, 我们也就拿到了方法的执行结果.
以上是方法或者说函数执行的过程, 但是这个过程中无时无刻充满了风险, 稍有不慎就会出现堆栈越界, 造成程序崩溃甚至系统的宕机. 当然, 堆栈越界不是一定会出现错误, 像堆栈溢出攻击就利用了堆栈的越界访问, 执行未经授权的方法或程序段.
.Net支持的C#语言是强类型的语言, 如果有一种机制能够安全的调用方法, 则即排除了风险又提高了程序执行的效率, 于是产生了委托.
委托的定义格式: public delegate 返回值 委托名称 (参数列表). 前面提到过方法的三要素: 返回值、方法名及参数. 委托规定了返回值和参数的类型, 在执行方法时会进行类型检查, 防止堆栈越界调用的问题. 那么还有一个要素(方法名), 委托如何处理呢? 委托处理方法名的手段体现了委托一个巨大的优势: 只需要很少的代码改动就能为程序更换方法, 程序的基本逻辑不变(有点类似于多肽的手法).
委托不能够直接调用(委托是类的类型), 可以直接调用的是委托实例, 而委托成为实例必须要绑定符合委托定义的方法. 下面看个小示例:
{
public delegate int MathDelegate( int one, int two); // 定义委托, 委托属于5大类型(类、结构、枚举、委托和接口)之一
public class MathClass
{
public int AddTwoNum( int onenum, int twonum) // 符合委托定义的方法
{
return onenum + twonum;
}
public int MultiTwoNum( int onenum, int twonum)
{
return onenum * twonum;
}
}
class Program
{
static void Main( string [] args)
{
// 创建类实例, 以便访问实例方法
MathClass mc = new MathClass();
// 创建委托实例
MathDelegate md = new MathDelegate(mc.AddTwoNum);
Console.WriteLine(md( 6 , 7 ));
// 改变与委托绑定的方法
md = new MathDelegate(mc.MultiTwoNum);
Console.WriteLine(md( 6 , 7 ));
}
}
}
现在我们来分析下委托的执行过程:
首先类属于引用类型, 我们定义的类会存放在堆中, 类中有两个引用分别指向类中定义的两个方法. 当我们用new创建实例的时候, 将在内存中开辟另外一块空间存放math类的实例(只为实例的数据成员创建空间), 同时将该实例的引用赋给栈中的变量m, 此时就可以通过m访问Math类中的方法成员.
委托也是引用类型, 因此同样存放在堆中, 委托类型中存放方法的返回值和参数类型的定义. 委托不能单独调用, 且创建委托实例时必须绑定方法. 当我们创建委托实例md时, 也会在内存中开辟空间, 委托实例首先从委托类型中获取方法返回值和参数的定义, 然后创建绑定方法的引用(该引用实际上是类中方法的引用), 之后将对被引用方法的进行检查.
匿名方法和Lambda表达式是C#3.0的新特性, Lambda表达式其实就是匿名方法换个写法而已. 那么关键点就变成--匿名方法是什么.
匿名方法就是没有方法名的委托实例, 匿名方法允许我们不用定义方法名就可以快速创建委托实例, 格式: delegate(参数列表){方法体}, 如实例代码:
{
return a + b;
};
Console.WriteLine(md3( 6 , 7 ));
Lambda表达式就是匿名方法的不同写法而已. 格式为: 参数列表 => 方法体
如下列代码:
Console.WriteLine(md4( 6 , 7 ));
md4 = (x,y) => x * y;
Console.WriteLine(md4( 6 , 7 ));
关于事件:
事件就是当对象或类的状态发生改变时, 对象或类发出的信息或通知. 通俗点讲, 所谓事件就是由某个对象发出的消息, 这个消息标志着某个特定的行为发生了, 或者某个特定的条件成立了.发出信息的对象或类称为”事件源”, 对事件进行处理的方法称为”事件监听者”, 通常”事件监听者”监听事件源发出的信息或通知, 一旦收到通知将执行相应的方法.
从本质上讲, 事件就是附加了安全限制的委托, 事件对委托做了一些限制和改进, 如: 禁止外界直接调用委托所绑定的方法, 禁止覆盖委托绑定的方法, 以及扩展了委托绑定方法的限制, 使委托可以绑定多个方法等.
那么, 如何用程序表示一个事件呢? 有5个步骤.
1. 定义委托: 事件的定义必须要指定一个委托类型.
2. 定义事件: 可以看做是委托变量加上一个event的前缀
3. 注册事件(很多时候也将注册事件放在构造函数中进行)
4. 定义响应事件的方法, 该方法需要符合委托定义, 即解决实际问题的方法.
5. 定义事件的触发方法: On事件名
代码示例如下:
{
// 1.定义委托
public delegate void KillEngine();
public delegate void ThiefEventHandler( object sender, EventArgs e); // 符合.Net命名规范的委托
public class ComputerRoom
{
// 2. 定义事件
public event KillEngine PowerOff;
public event ThiefEventHandler ThiefEvent; // 符合.Net命名规范的事件
// 3.1 构造函数中注册事件
public ComputerRoom()
{
this .PowerOff += new KillEngine(ShutPc);
this .PowerOff += new KillEngine(ShutWaterMachine);
this .PowerOff += new KillEngine(ShutLightsOff);
}
// 4.1 响应事件的处理方法
void ShutPc()
{
Console.WriteLine( " 所有计算机已关闭! " );
}
void ShutWaterMachine()
{
Console.WriteLine( " 饮水机已关闭! " );
}
void ShutLightsOff()
{
Console.WriteLine( " 所有照明设备已关闭! " );
}
// 5. 定义事件的触发方法
public void OnPowerOff()
{
if ( this .PowerOff != null )
{
this .PowerOff();
}
}
public void OnThiefEvent(EventArgs e)
{
if ( this .ThiefEvent != null )
{
this .ThiefEvent( this , EventArgs.Empty);
}
}
}
class Program
{
static void Main( string [] args)
{
ComputerRoom cr = new ComputerRoom();
// 3.2 通过类的实例注册事件
cr.ThiefEvent += new ThiefEventHandler(Call110);
cr.ThiefEvent += new ThiefEventHandler(ShutDoors);
// 发生了事件, 事件被触发
cr.OnPowerOff();
Console.WriteLine( " \n------------------------------------\n " );
cr.OnThiefEvent( null );
}
// 4.2 在类外的响应事件的方法
static void Call110( object sender, EventArgs e)
{
// throw new NotImplementedException();
Console.WriteLine( " 机房被非法入侵, 已通知110! " );
}
static void ShutDoors( object sender, EventArgs e)
{
Console.WriteLine( " 所有门窗已关闭! " );
}
}
}