目录
1 .NET 事件处理机制的剖析与应用
1.1 直观理解 “事件” 的概念
事件建立于委托的基础之上。
从面向对象角度来说,事件是由对象发出的消息,它是一个信号,通知其它对象有事情发生。例如,用户单击窗体上的某个控件时,控件可能会激发一个 Click 事件。
激发与响应事件的载体都是对象。激发事件的对象被称为 “事件源”,对这个事件进行响应的对象称为 “响应者”,响应者必须提供一个 “事件响应(或处理)方法”。
1.2 事件与委托的关系
事件的激发必须由事件源对象自己引发,不应该允许由外界引发。为了限制事件的激发只能由事件源对象自己引发,C# 引入了一个新的关键字 —— event。
在定义委托成员的时候给出 event 关键字进行修饰,前面加了 event 关键字修饰的 public 委托成员,只能在类外部进行附加和移除操作,而调用操作只能发生在类的内部。
public partial class Form1 : Form
{
Form2 m_objForm2 = new Form2();
public Form1()
{
InitializeComponent();
m_objForm2.Show();
m_objForm2.eventClick += OnCount; // 使用事件方式实现回调
// m_objForm2.eventClick(10); // Error:不允许外界直接触发事件
}
private void OnCount(int nCount)
{
m_txt_Count.Text = nCount.ToString();
}
private void m_hsb_Num_Scroll(object sender, ScrollEventArgs e)
{
m_objForm2.OnChange(m_hsb_Num.Value);
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public event Action<int> eventClick;
private int nCount = 0;
private void m_btn_Event_Click(object sender, EventArgs e)
{
nCount++;
if (eventClick != null) // 在激发事件前,判断事件是否为空
{
eventClick(nCount); // 激发事件
}
}
public void OnChange(int nNum)
{
m_pBar_Num.Value = nNum;
}
}
调用加了 event 关键字修饰的委托也称为 “激发事件”,调用方称为 “事件发布者”,被调用方称为 “事件注册者”,附加委托的过程称之为 “注册事件”,移除委托的过程称之为 “注销事件”。通过委托调用的方法称为 “事件处理程序”。
1.3 .NET 事件实现机制剖析
使用Visual Studio 创建一个 Windows Forms 应用程序,往窗体上拖一个按钮控件 button1,双击按钮,将会在 Form1.cs 文件中生成一个事件响应方法框架:
private void button1_Click(object sender, EventArgs e)
{ ... }
注意这一方法有两个参数,第一个参数 sender 代表了事件源对象,第二个参数 e 代表与事件相关的信息。
相应的,“Form1.Designer.cs” 文件中会增加一行:
this.button1.Click += new System.EventHandler(this.button1_Click);
上述代码中的 “Click” 是按钮基类 Control 的成员,它被定义为一个事件:
public event EventHandler Click;
Click 事件定义中所用到 “EventHandler” 是 .NET Framework 基类库所提供的一个预定义委托,其声明如下:
public delegate void EventHandler(object sender, EventArgs e);
1.4 委托链表的分步调用
当我们调用委托链时,如果某一个委托对应的方法抛出了异常,那么剩下的其它委托将不再调用。为了解决这个问题,我们需要分步调用每个委托,将每一步的调用代码均放在 try/catch 块中。
public void DoSomething()
{
Action tmp = SomeEvent;
if (tmp != null)
{
Delegate[] delegates = tmp.GetInvocationList();
foreach (Delegate d in delegates)
{
Action del = d as Action;
try
{
del();
}
catch (System.Exception ex)
{
}
}
}
}
上述代码中,我们没有直接使用 tmp 来调用委托链表,而是先通过 tmp.GetInvocationList 方法来获取委托链表中的委托集合,然后再使用 foreach 循环遍历集合,分布调用每个委托,分步调用过程均放在了 try/catch 块中,这样一来,任何一个方法抛出异常都不会影响到其它委托的调用。