事件和委托的区别

事件和委托的区别c# 2009-12-01 10:37:51 阅读99 评论0 字号:大中小
事件其实没什么不好理解的,声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已

委托的主要用途是方法调用、函数回调和事件,而事件重要用于外发消息。

文章出处:https://www.360docs.net/doc/9d1545580.html,/course/4_webprogram/https://www.360docs.net/doc/9d1545580.html,/asp_netshl/2008102/147320.html

C# 中的委托和事件 https://www.360docs.net/doc/9d1545580.html,/JimmyZhang/archive/2007/09/23/903360.html

C#中的委托和事件(续) https://www.360docs.net/doc/9d1545580.html,/JimmyZhang/archive/2008/08/22/1274342.html





1. NET委托及应用


1.1 .NET委托概念
OOP中具有相同属性的对象抽象后成为类型(class)。那么,具有相同属性的函数或方法(也称具有相同的函数签名):

返回类型相同
参数类型、参数顺序及参数个数相同
抽象后又是什么概念?例如,1到n之间每个数的平方后求和函数int SquareSum(int n)和立方后求和函数int CubeSum(int n),它们具有相同的函数签名:返回类型int、参数只有一个且是int类型。static private int SquareSum(int n)
{
int m = 0;
for (int k = 1; k <= n; k++)
{
m += k * k;
}
return m;
}static private int CubeSum(int n)
{
int m = 0;
for (int k = 1; k <= n; k++)
{
m += k * k * k;
}
return m;
}
这些相同属性的函数抽象,就是.NET提出的一个新的类型概念——委托,关键字为delegate。

1.2 .NET委托声明及特点
与C/C++/C#的函数声明相同,声名一个委托需要有:委托名、返回类型、参数及类型。例如,声明前面定义的两个函数的委托PowerSum如下:
public delegate int PowerSum(int n);特别地,一类通用的事件处理委托EventHandler声明如下:
public delegate void EventHandler(object sender, EventArgs e)显然,与类定义不同,委托声名不需要定义成员,它只起一个表示作用(delegate就是代表的意思)。此外,delegate也是类,其基类是MulticastDelegate,再上层类是Delegate,顶层类是object。

1.3 .NET委托应用描述
Microsoft .NET Framework 通过委托向外提供一种函数回调机制。——《框架设计(第2版)》Jeffrey Richeter
是一种类型安全的方法引用,可以把它看成一个类型安全的C函数指针。——《.NET组件编程》Juval Lowy
要把方法传送给其它方法时需要委托。与C函数指针不同,.NET委托是类型安全的。——《C#高级编程》Christian Nagel
从上面名著的描述可以看出,.NET委托的主要用途是三个:1)函数回调;2)传递方法;3)事件机制。

1.4 .NET委托举例1:传递方法
委托作为方法传递时,有两种方式。第一种,直接传递方法,这种方式称为委托推断;第二种,创建委托对象后传递,这种方式是常规方式。应用前面定义的委托,

现定义一个调用方法的函数:int GetPowerSum(PowerSum ps)如下,该函数用于计算1到10的指数和。
static private int GetPowerSum(PowerSum ps)
{
return ps(10);
}采用委托推断方式调用代码如下:
int p2 = GetPowerSum(SquareSum);
int p3 = GetPowerSum(CubeSum);采用创建委托对象方式调用代码如下:
PowerSum ps2 = new PowerSum(SquareSum);
PowerSum ps3 = new PowerSum(CubeSum);

p2 = ps2(10);
p3 = ps3(10);

1.5 .NET委托举例2:函数回调
最常见的回调应用之一,是计时器到点时调用的函数。涉及到的类型如下(.NET有三个计时器类型,这个是线程名称空间System.Threading里的Timer):
public sealed Timer(TimerCallBack callback, object state, int dueTime, int period);
public delegate void TimeCallBack(object state);
Timer类型中,callback是一个委托TimerCallBack的对象;state是调用时的状态参数,可以灵活应用;dueTime是计时器开始计时的等待时间;period是计时周期,每完成一个周期就调用方法callback
回调函数CalllBack的委托定义表明,计时器类Timer到点时回调的函数不能有返回类型,但必须有一个参数object型的参数。注意,此处委托的所谓逆变不能用了

现定义一个到点回调函数,即到点就输出字符串信息如下:
static private void TimeClick(object state)
{
Console.WriteLine("time click 500ms");
}那么500ms报时的计时器应用代码如下:
System.Threading.TimerCallback callBack = new System.Threading.TimerCallback(TimeClick);
System.Threading.Timer timer = new System.Threading.Timer(callBack, null, 0, 500);由于回调函数比较简单,可以使用匿名委托,代码如下
System.Threading.TimerCallback callBack = new System.Threading.TimerCallback
(
delegate(object state)
{
Console.WriteLine("time click 500ms");
}
);

System.Threading.timer = new System.Threading.Timer(callBack, null, 0, 500);

2 .NET事件及应用


2.1 .NET事件概念
一个如何获得另一个对象发生某个事件的通知?VB和C#中常用的方法如下

VB按钮(Command)点击事件:Sub Command1_Click()
C#按钮(Button)点击事件:void button1_Click(object sender, EventArgs e)
这表明,事件是一种信号机制,对象在发生某种活动时自动发出通知,是对象定义的外发消息接口。其它对象若对事件感兴趣,则为该事件注册一个事件处理程序。事件发生时,所有注册在该事件上的处理程序都会被调用。

发布事件的对象称为发布者(publisher)或事件源,发布事件也称为激发(fire)事件。关注事件的对象称为事件接收器(sinker)或订阅者(subscriber),订阅事件也称为注册事件方法。发布者调用订阅者的注册方法。.NET事件模型建立在委托机制之上,支持事件定义、发布、订阅、和拆除。

2.2 设计.NET事件5个步骤
定义参数类型

:从类型EventArgs派生出满足要求的事件参数类
定义事件处理者委托:与第1)步相关,该步一般被泛型委托取代了
定义事件成员:在自定义类中,由事件处理者委托定义一个或多个事件成员
激发事件:在自定义类的引发事件方法中,通知所有事件订阅者
订阅事件:其它对象注册事件处理程序
需要指出,上述第3、4、5步必须存在,第1、2步可适当省略:

第2步可省。如果采用标准事件处理者委托类型:void EventHandler(object sender, EventArgs e),那么只需要第1步给出事件参数,然后使用泛型委托:EventHandler即可定义类的事件成员了。其中,T就是事件参数类型
如果没有自定义事件参数,可以省略第1、2步,直接用EventHandler定义类的事件成员

2.3 事件设计举例
编写一个统计按键次数的键盘侦听类TKeyListen
TKeyListen可以发布侦听到的击键次数,并检查返回参数值
注册事件的对象可以终止侦听循环
第1步:定义事件参数类
public class KeyEventArgs : EventArgs
{
private int m_KeyCount;
private bool m_Stop = false;

public KeyEventArgs(int keyCount) // 发布事件时给出按键计数值
{
m_KeyCount = keyCount;
}

public int KeyCount
{
get { return m_KeyCount; }
}

public bool Stop
{
get { return m_Stop; }
set { m_Stop = value; } // 事件订阅者可以修改
}
}
第2步:声明事件处理者委托
public delegate void KeyEventHandler(object sender, KeyEventArgs e);实际使用时,除非上述委托有其它用途,一般使用泛性委托EventHandler取代,其中T就是事件参数类型。

第3、4步:定义类事件成员、激发(发布)事件
public class TKeyListen
{
// public event KeyEventHandler KeyPress; // 第3步:定义事件成员
public event EventHandler KeyPress; // 第3步:泛型委托实现

public void Listen()
{
Console.WriteLine("Please press key.");
int keyCount = 0;

while (true) // 使用循环监听击键动作
{
ConsoleKeyInfo key = Console.ReadKey();
keyCount++;

if (KeyPress != null) // 如果存在订阅者,即:委托链非空
{
KeyEventArgs e = new KeyEventArgs(keyCount);
KeyPress(this, e); // 第4步:激发事件,通知所有订阅者

if (e.Stop) // 判断事件返回参数
{
break;
}
}
}
}
}上述代码包含了事件实现的第3、4步。其中循环代码while(true)包含了发布(激发)事件,特别说明如下:

必须判断委托链(订阅者链)是否空,即是否存在事件的订阅者:if (KeyPress != null)。如

果没有事件订阅者,直接发布事件KeyPress(this,e),系统将抛出异常“未将对象引用设置到对象实例上”
激发或发布事件时,第一个参数是对象自己(this):KeyPress(this, e)

KeyPress(this,e)实际执行过程:遍历事件订阅者链,执行每个订阅事件方法,这些方法具有与KeyPress相同的委托类型
可以判断事件返回参数,即订阅者可以修改参数e.Stop,发布者检测该参数。如果有多个订阅者,上述代码只获得最后一个订阅事件处理方法给定的参数。如果要判断每个订阅方法的参数,必须使用委托的GetInvocationList()方法,逐个获得返回参数。
第5步:订阅事件
static void Main(string[] args)
{
TKeyListen demo = new TKeyListen();
demo.KeyPress += CountKey; // 订阅事件,使用委托推断方式
demo.Listen();
}

static void CountKey(object sender, KeyEventArgs e) // 事件处理方法
{
Console.WriteLine("Press count: " + e.KeyCount);
if (e.KeyCount == 5) e.Stop = true; // 5次后停止
}注意,上述代码中,事件处理方法CountKey必须与事件处理者委托或泛型委托一致。此外,需要说明如下:

订阅事件操作符为:+=,移除订阅操作为-=
建立委托对象订阅方式:demo.KeyPress += new KeyEventHandler(CountKey);

可以多次订阅,从而产生事件链:demo.KeyPress += delegate() {…}
小结:委托的主要用途是方法调用、函数回调和事件,而事件重要用于外发消息。

相关文档
最新文档