笔记选自书《.NET4.0面向对象编程漫谈 基础篇》的章节——事件及事件驱动
Tag:事件与多路委托
从面向对象角度来说,事件是由对象发出的信息,它是一个信号,通知其他对象有事情发生。
激发与响应事件的载体都是对象。激发对象的对象被称为“事件源”,对这个事件进行响应的对象称为“响应者”,响应者必须提供一个“事件响应(或处理)方法”。
事件与多路委托
事件的主要特点是一对多关联,即一个事件可以有多个响应者。.NET的事件处理机制是基于多路委托实现的。
多路委托实现事件的例子:
public delegate void MyEventDelegate(int value);
public class EventSource
{
public MyEventDelegate handles;
}
public class EventResponsor
{
public void MyMethod(int i)
{
Console.WriteLine(i);
}
}
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
EventSource p=new EventSource();
EventResponsor responsor1=new EventResponsor();
EventResponsor responsor2=new EventResponsor();
p.handles=System.Delegate.Combine(p.handles,new MyEventDelegate(responsor1.MyMethod)) as MyEventDelegate;
p.handles=System.Delegate.Combine(p.handles,new MyEventDelegate(responsor2.MyMethod)) as MyEventDelegate;
//p.handles+=new MyEventDelegate(responsor1.MyMethod);
//p.handles+=new MyEventDelegate(responsor2.MyMethod);
//or more brief ways
//p.handles+=responsor1.MyMethod;
//p.handles+=responsor2.MyMethod;
p.handles(11);
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
先定义一个委托,接着定义事件源EventSource类,字段MyEventDelegate类型的handles为它的响应者清单,定义一个事件响应者类EventResponsor,里面包含了需要用来响应的方法
主函数中,声明一个事件源对象p和两个响应者responsor1和responsor2,然后调用Delegate类的静态方法组合多个委托变量(或者直接新建一个委托变量),最后手动触发事件,运行程序可以看到,有两个反应
上述例子中事件是手动触发的,而真实的事件不允许由外界触发,必须由事件源对象自己触发,为此引入关键字event,改动例子:
public delegate void MyEventDelegate(int value);
public class EventSource
{
public event MyEventDelegate handles;
public void FireEvent()
{
if(handles!=null)
handles(11);
}
}
public class EventResponsor
{
public void MyMethod(int i)
{
Console.WriteLine(i);
}
}
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
EventSource p=new EventSource();
EventResponsor responsor1=new EventResponsor();
EventResponsor responsor2=new EventResponsor();
p.handles+=new MyEventDelegate(responsor1.MyMethod);
p.handles+=new MyEventDelegate(responsor2.MyMethod);
//or more brief ways
//p.handles+=responsor1.MyMethod;
//p.handles+=responsor2.MyMethod;
p.FireEvent();
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
改动地方:EventSource类的字段handles字段加了关键字event,添加了一个触发方法FireEvent(),组合委托变量时不能用Delegate的静态方法了,必须用追加委托的方法+=,触发事件的方法也变为引用事件源类提供的触发方法FireEvent()。
.NET事件实现机制剖析
书的作者进行了很认真的剖析,我记一下大致要点
给Form里面的Button添加事件后,在Initialize方法可以看到:
Button btn = new Button();
btn.MouseClick+=new MouseEventHandler(btn_MouseClick);
上述代码中的MouseClick是Button类的事件成员:
Button本身定义的事件只有两个,其它都继承自ButtonBase和IButtonControl,作用如上例中的handles,所以可以给Button添加多个响应函数。
作者的总结:所有的.NET可视化窗体控件的预定义事件,都是某一对应的“事件名+Handler”委托类型的变量。与此事件相关的信息封装在“事件名+Args”类型的事件参数中,此事件参数对象派生自EventArgs。
定义自己的事件:
自定义事件的基本方法步骤:
1、创建一个事件专用委托,此委托定义了事件响应方法的签名。
2、使用event关键字为对象定义一个事件字段
3、在合适的地方激发事件
示例代码:
从窗体:从窗体有一个按钮,为此按钮添加一个事件
public delegate void ValueChanged(string value);
public event ValueChanged ButtonClicked;
private int counter;
void button1_Click(object sender, EventArgs e)
{
counter++;
if (ButtonClicked != null)
ButtonClicked(counter.ToString());
}
主窗体:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
labelInfo = new Label();
labelInfo.Dock = DockStyle.Fill;
labelInfo.Font = new Font("Arail", 14);
labelInfo.BackColor = Color.YellowGreen;
labelInfo.TextAlign = ContentAlignment.MiddleCenter;
this.Controls.Add(labelInfo);
ChildForm c = new ChildForm();
c.ButtonClicked += ShowInfo;
c.Show();
}
private void ShowInfo(string value)
{
labelInfo.Text = value.ToString();
}
分析:主窗体定义委托、事件、事件、事件触发机制,主窗体给从窗体的事件容器添加自定义事件函数。事件可以看成是从窗体类的一个属性或字段,到主窗体初始化从窗体类的时候给它的事件属性赋方法的值。
事件访问器:
默认情况下,当类声明事件时,编译器会将内存分配给一个事件字段,以存储事件信息。如果类具有许多未使用的事件,则它们会不必要的占用内存。
对于这种情况,可以使用.NET Framework提供的一个EventHandlerList类来减少内存占用。
EventHandlerList类可以看成是一个事件的集合,由它来保存各种事件的响应方法列表。
EventHandlerList对象可以保存多个事件的多个响应方法,不同的事件通过时间名字区分。只有需要响应的事件才拥有方法调用列表,才会在EventHandlerList对象中出现。
如果使用EventHandlerList对象来保存事件的响应方法,必须为每个事件编写特殊的时间访问器:
public delegate void OneDelegate(int value);
public class A
{
private EventHandlerList events=new EventHandlerList();
public event OneDelegate Event1
{
add{events.AddHandler("Event1",value);}
remove{events.RemoveHandler("Event1",value);}
}
}
可以看到,事件Event1不再是一个简单的带有event关键字的共有委托字段,而是一个事件类型的属性,add和remove为此事件属性的关键字。
激发事件:
定义了时间访问器的事件的激发方法与原先不同
(EventHandlerList对象名[事件名] as 定义事件的委托类型)(参数)
例子:
(events["Event1"] as OneDelegate)(100);