[转]对.NET Framework "事件"机制理解的代码分析
对"事件"机制理解
下面的文章是我自认为对"事件"机制理解比较透彻的后写的代码分析,但写完之后,鄙人又惟恐理解有所偏差,所以特贴出来让各位高手指教一二,若能让发现理解错误之处,将不胜感激(此问完全乃本人"独立自主"之作,绝非抄袭) 同时我相信此文对c#初学者也有一定帮助! 为了阐述清晰,特举例说明;
该范例是在一个控件中完全自定义一组事件,并在另外的程序集中对事件被激发作出反映(也就是事件被激发后调用预先定义好的方法).
一.含有自定义事件的控件主体代码及对应剖析 (注意,此控件库是由VS.NET的"新建"->"Windows控件库"生成的)
Code
namespace MyEventTEST
{
public class LoginEventArgs : System.EventArgs
// 上面代码定义了在主程序中引发事件时需要传递给主程序的所有信息,并且注意,
// 该类必须派生于System.EventArgs类
{
public LoginEventArgs(string sUserID, string sPassword, bool bValid)
{
UserID = sUserID;
Password = sPassword;
Valid = bValid;
}
public string UserID;
public string Password;
public bool Valid;
}
public delegate void GoodLoginEventHandler(object sender, LoginEventArgs e);
public delegate void FailedThreeTimesEventHandler(object sender, LoginEventArgs e);
// 上面两行代码定义了两个多路委托(因此返回类型必须为void),每个委托对应一种类型的事件;
// 因为是多路委托,所以每个委托中可以含有多个方法.
// 请注意,参数是(object sender, LoginEventArgs e),所以添加到多路委托的方法必须符合这种签名方式.
// 此外,为什么这里不使用系统已经定义的多路委托"System.EventHandler(object sender, EventArgs e)",
// 而要自己定义新的委托"?????EventHandler()"呢?这是因为我们这里传递给用户程序集的参数不是
// "System.EventArgs"类型,而是自己定义的"LoginEventArgs"类型,所以有必要重新定义自己的委托类型.
public class ActiveLogin : System.Windows.Forms.UserControl
{
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox txtUserID;
private System.Windows.Forms.TextBox txtPass;
private System.Windows.Forms.Button btnLogin;
private System.Windows.Forms.Button btnCancel;
private System.ComponentModel.Container components = null;
// 上面代码是组成这个控件的一些组件定义,由VS.NET自动生成
}
public event GoodLoginEventHandler GoodLogin;
public event FailedThreeTimesEventHandler FailedThreeTimes;
public event EventHandler Cancel;
// 上面三行代码非常之重要,定义了三个事件(event),分别是"GoodLogin","FailedThreeTimes"
// 和"Cancel"
// 它们的类型分别是"GoodLoginEventHandler","FailedThreeTimesEventHandler"
// 和"EventHandler",也就是说添加到这三个事件中的方法必须符合对应的多路委托定义!
// 而且注意,因为事件"Cancel"的类型是系统已经定义的多路委托"EventHandler"类型,
// 所以上面的多路委托中没有定义类似"CancelEventHandler"的新委托,因为是不需要的.
public ActiveLogin()
{
InitializeComponent();
}
// 上面代码是控件中类"ActiveLogin"的构造方法,该方法中调用了初始方法InitializeComponent()
// 上面代码由VS.NET自动生成
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
// 上面代码是自定义控件中类"ActiveLogin"的析构方法,由VS.NET自动生成.
private void InitializeComponent()
{
. // 这里是对所有引用控件(组件)的初始化代码
}
// 上面代码是自定义控件中类"ActiveLogin"的初始方法,其中内容由VS.NET自动生成.
protected virtual void OnGoodLogin(LoginEventArgs e)
// 上面一行代码定义了激发"GoodLogin"事件的方法;
// 注意签名类型,定义方法是protected virtual,也就是说只能在这个类及它的
// 继承类中访问此方法,而且可以重写.
// 参数类型是"LoginEventArgs",注意只有这一个参数,因为在本方法中含有对
// this的引用,所以这里不需要传递this对象.
// 一般地说,这个方法使用场合只有两种:
// <1>在本控件内被调用,因为本方法不被调用,就无法激发用户代码在事件"GoodLogin"
// 中添加的方法;
// <2>在用户的继承代码中重写本方法,虽然重写本方法可能会带来性能的提高,但
// 倘若用户代码中忘记调用此方法,那么在用户代码先前在事件"GoodLogin"中
// 添加的方法将无法得到激活!!! (避免此问题的方法就是在重写方法中必须含有
// 一行"base.GoogLogin(e)",这行将负责调用本方法)
// 对于第<2>点需要提出的是,在用户的继承代码中重写本方法的作用相当与在
// 事件"GoodLogin"中添加一个方法,此方法的代码内容和重写方法内容相同.
// (但是应该绝对没有"base.GoogLogin(e)"这一行)
{
if (GoodLogin != null) // 如果在事件"GoogLogin"中含有方法,则激发这些方法
{
GoodLogin(this, e); // 把this对象和参数e传递给所有在事件"GoogLogin"
// 中添加的方法,并顺序执行这些方法 (注意,由多路委托特性决定:在用户代码中先添加的方法先执行.
}
}
// 上面对OnGoogLogin方法解释已经十分详细了,下面两个ON方法均与上述ON方法同出一辙.
protected virtual void OnFailedThreeTimes(LoginEventArgs e)
{
if (FailedThreeTimes != null)
{
FailedThreeTimes(this, e);
}
}
protected virtual void OnCancel(System.EventArgs e)
{
if (Cancel != null)
{
Cancel(this, e);
}
}
private void btnLogin_Click(object sender, System.EventArgs e)
// 上面的定义是由VS.NET自动生成,是当按下控件的"btnLogin"按钮时调用的方法.
{
if()
{
OnGoodLogin(new LoginEventArgs(txtUserID.Text, txtPass.Text, true));
}
// 上面一行代码调用了OnGoodLogin方法,作用是"当控件中的按钮btnLogin被按下时,
// 并且符合上面的if条件时:
// 将通过调用OnGoodLogin方法把在用户代码中添加到事件"GoogLogin"中的所有方法
// 全部顺序执行一遍.
// 为什么不在这里把OnGoodLogin()方法中的代码执行一遍,而还要再单独调用OnGoodLogin
// 方法呢? 这是因为有时候用户代码需要重写OnGoodLogin()方法!
// 下面调用的OnFailedThreeTimes()方法和OnCancel()方法解释同上.
else
{
OnFailedThreeTimes(new LoginEventArgs(txtUserID.Text, txtPass.Text, false));
}
}
private void btnCancel_Click(object sender, System.EventArgs e)
{
OnCancel(new EventArgs());
}
}
namespace MyEventTEST
{
public class LoginEventArgs : System.EventArgs
// 上面代码定义了在主程序中引发事件时需要传递给主程序的所有信息,并且注意,
// 该类必须派生于System.EventArgs类
{
public LoginEventArgs(string sUserID, string sPassword, bool bValid)
{
UserID = sUserID;
Password = sPassword;
Valid = bValid;
}
public string UserID;
public string Password;
public bool Valid;
}
public delegate void GoodLoginEventHandler(object sender, LoginEventArgs e);
public delegate void FailedThreeTimesEventHandler(object sender, LoginEventArgs e);
// 上面两行代码定义了两个多路委托(因此返回类型必须为void),每个委托对应一种类型的事件;
// 因为是多路委托,所以每个委托中可以含有多个方法.
// 请注意,参数是(object sender, LoginEventArgs e),所以添加到多路委托的方法必须符合这种签名方式.
// 此外,为什么这里不使用系统已经定义的多路委托"System.EventHandler(object sender, EventArgs e)",
// 而要自己定义新的委托"?????EventHandler()"呢?这是因为我们这里传递给用户程序集的参数不是
// "System.EventArgs"类型,而是自己定义的"LoginEventArgs"类型,所以有必要重新定义自己的委托类型.
public class ActiveLogin : System.Windows.Forms.UserControl
{
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox txtUserID;
private System.Windows.Forms.TextBox txtPass;
private System.Windows.Forms.Button btnLogin;
private System.Windows.Forms.Button btnCancel;
private System.ComponentModel.Container components = null;
// 上面代码是组成这个控件的一些组件定义,由VS.NET自动生成
}
public event GoodLoginEventHandler GoodLogin;
public event FailedThreeTimesEventHandler FailedThreeTimes;
public event EventHandler Cancel;
// 上面三行代码非常之重要,定义了三个事件(event),分别是"GoodLogin","FailedThreeTimes"
// 和"Cancel"
// 它们的类型分别是"GoodLoginEventHandler","FailedThreeTimesEventHandler"
// 和"EventHandler",也就是说添加到这三个事件中的方法必须符合对应的多路委托定义!
// 而且注意,因为事件"Cancel"的类型是系统已经定义的多路委托"EventHandler"类型,
// 所以上面的多路委托中没有定义类似"CancelEventHandler"的新委托,因为是不需要的.
public ActiveLogin()
{
InitializeComponent();
}
// 上面代码是控件中类"ActiveLogin"的构造方法,该方法中调用了初始方法InitializeComponent()
// 上面代码由VS.NET自动生成
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
// 上面代码是自定义控件中类"ActiveLogin"的析构方法,由VS.NET自动生成.
private void InitializeComponent()
{
. // 这里是对所有引用控件(组件)的初始化代码
}
// 上面代码是自定义控件中类"ActiveLogin"的初始方法,其中内容由VS.NET自动生成.
protected virtual void OnGoodLogin(LoginEventArgs e)
// 上面一行代码定义了激发"GoodLogin"事件的方法;
// 注意签名类型,定义方法是protected virtual,也就是说只能在这个类及它的
// 继承类中访问此方法,而且可以重写.
// 参数类型是"LoginEventArgs",注意只有这一个参数,因为在本方法中含有对
// this的引用,所以这里不需要传递this对象.
// 一般地说,这个方法使用场合只有两种:
// <1>在本控件内被调用,因为本方法不被调用,就无法激发用户代码在事件"GoodLogin"
// 中添加的方法;
// <2>在用户的继承代码中重写本方法,虽然重写本方法可能会带来性能的提高,但
// 倘若用户代码中忘记调用此方法,那么在用户代码先前在事件"GoodLogin"中
// 添加的方法将无法得到激活!!! (避免此问题的方法就是在重写方法中必须含有
// 一行"base.GoogLogin(e)",这行将负责调用本方法)
// 对于第<2>点需要提出的是,在用户的继承代码中重写本方法的作用相当与在
// 事件"GoodLogin"中添加一个方法,此方法的代码内容和重写方法内容相同.
// (但是应该绝对没有"base.GoogLogin(e)"这一行)
{
if (GoodLogin != null) // 如果在事件"GoogLogin"中含有方法,则激发这些方法
{
GoodLogin(this, e); // 把this对象和参数e传递给所有在事件"GoogLogin"
// 中添加的方法,并顺序执行这些方法 (注意,由多路委托特性决定:在用户代码中先添加的方法先执行.
}
}
// 上面对OnGoogLogin方法解释已经十分详细了,下面两个ON方法均与上述ON方法同出一辙.
protected virtual void OnFailedThreeTimes(LoginEventArgs e)
{
if (FailedThreeTimes != null)
{
FailedThreeTimes(this, e);
}
}
protected virtual void OnCancel(System.EventArgs e)
{
if (Cancel != null)
{
Cancel(this, e);
}
}
private void btnLogin_Click(object sender, System.EventArgs e)
// 上面的定义是由VS.NET自动生成,是当按下控件的"btnLogin"按钮时调用的方法.
{
if()
{
OnGoodLogin(new LoginEventArgs(txtUserID.Text, txtPass.Text, true));
}
// 上面一行代码调用了OnGoodLogin方法,作用是"当控件中的按钮btnLogin被按下时,
// 并且符合上面的if条件时:
// 将通过调用OnGoodLogin方法把在用户代码中添加到事件"GoogLogin"中的所有方法
// 全部顺序执行一遍.
// 为什么不在这里把OnGoodLogin()方法中的代码执行一遍,而还要再单独调用OnGoodLogin
// 方法呢? 这是因为有时候用户代码需要重写OnGoodLogin()方法!
// 下面调用的OnFailedThreeTimes()方法和OnCancel()方法解释同上.
else
{
OnFailedThreeTimes(new LoginEventArgs(txtUserID.Text, txtPass.Text, false));
}
}
private void btnCancel_Click(object sender, System.EventArgs e)
{
OnCancel(new EventArgs());
}
}
二.调用此控件的程序集(注意,此程序集是由VS.NET的"新建"->"Windows应用程序"生成的),也就是"用户代码"部分
Code
namespace HostApp
{
public class Form1 : System.Windows.Forms.Form
{
private MyEventTEST.ActiveLogin activeLogin1;
// 上面一行代码引用了自定义控件库的类"ActiveLogin",并用它定义了一个对象"activeLogin1".
// 这里的"MyEventTEST"是在自定义的控件库中定义的命名空间,如果在这个程序集中没有出现
// "using MyEventTEST"语句,则该名称必须出现在对自定义控件引用的任何代码中!
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
// 上面两个方法都是又VS.NET自动生成,不做另外解释.
private void InitializeComponent()
{
this.activeLogin1 = new MyEventTEST.ActiveLogin();
// 上面一行代码用自定义控件库中的类"ActiveLogin"实例化对象"activeLogin1"
this.SuspendLayout();
//
// activeLogin1
//
this.activeLogin1.Location = new System.Drawing.Point(144, 8);
this.activeLogin1.Name = "activeLogin1";
this.activeLogin1.Size = new System.Drawing.Size(280, 184);
this.activeLogin1.TabIndex = 0;
this.activeLogin1.GoodLogin += new MyEventTEST.GoodLoginEventHandler(this.activeLogin1_GoodLogin);
this.activeLogin1.Cancel += new System.EventHandler(this.activeLogin1_Cancel);
this.activeLogin1.FailedThreeTimes += new MyEventTEST.FailedThreeTimesEventHandler(this.activeLogin1_FailedThreeTimes);
// !!请注意上面的三行代码,这是用户代码接受自定义控件库中事件的代码 !
// 上面三行代码分别把用户定义的方法"activeLogin1_GoodLogin","activeLogin1_Cancel"
// 和"activeLogin1_FailedThreeTimes"分别添加到自定义控件中的事件"GoogLogin","Cancel"
// 和"FailedThreeTimes"中; 这样一来只要自定义控件中的对应事件一被激发,这些添加用户自定义方法就会被执行.
// 要注意的是,用户自定义方法签名必须符合对应的多路委托的定义(因为事件是由多路委托
// 定义的,所以要添加到什么事件,定义就必须符合该事件对应的多路委托的定义)
// 而且,这里的Cancel事件类型是系统已经定义的多路委托"EventHandler"类型,所以它的实例化
// 与其它两个事件的实例化有所不同,是"System.EventHandler",而不是"MyEventTEST.EventHandler"!
// 这些用户自定义方法将在下面列出.
// 不过请注意,上面的三行代码虽然在方法InitializeComponent()中,但却是我们自己手工添加的!
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(440, 357);
this.Controls.AddRange(new System.Windows.Forms.Control[] {this.activeLogin1, });
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
// 上面的方法分别是Windows Forms程序的入口.
private void activeLogin1_GoodLogin(object sender, MyEventTEST.LoginEventArgs e)
{
MessageBox.Show("Good Login! " + e.UserID);
}
private void activeLogin1_FailedThreeTimes(object sender, MyEventTEST.LoginEventArgs e)
{
MessageBox.Show("Failed to login three times.");
}
private void activeLogin1_Cancel(object sender, System.EventArgs e)
{
MessageBox.Show("Cancel");
}
// 上面的三个方法(activeLogin1_GoogLogin,activeLogin1_Cancel和activeLogin1_FailedThreeTimes)
// 就是当自定义控件中对应的事件被激发时在当前程序集中对应的处理方法.
// 值得注意的是,签名应该完全符合对应的多路委托的定义!
}
}
namespace HostApp
{
public class Form1 : System.Windows.Forms.Form
{
private MyEventTEST.ActiveLogin activeLogin1;
// 上面一行代码引用了自定义控件库的类"ActiveLogin",并用它定义了一个对象"activeLogin1".
// 这里的"MyEventTEST"是在自定义的控件库中定义的命名空间,如果在这个程序集中没有出现
// "using MyEventTEST"语句,则该名称必须出现在对自定义控件引用的任何代码中!
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
// 上面两个方法都是又VS.NET自动生成,不做另外解释.
private void InitializeComponent()
{
this.activeLogin1 = new MyEventTEST.ActiveLogin();
// 上面一行代码用自定义控件库中的类"ActiveLogin"实例化对象"activeLogin1"
this.SuspendLayout();
//
// activeLogin1
//
this.activeLogin1.Location = new System.Drawing.Point(144, 8);
this.activeLogin1.Name = "activeLogin1";
this.activeLogin1.Size = new System.Drawing.Size(280, 184);
this.activeLogin1.TabIndex = 0;
this.activeLogin1.GoodLogin += new MyEventTEST.GoodLoginEventHandler(this.activeLogin1_GoodLogin);
this.activeLogin1.Cancel += new System.EventHandler(this.activeLogin1_Cancel);
this.activeLogin1.FailedThreeTimes += new MyEventTEST.FailedThreeTimesEventHandler(this.activeLogin1_FailedThreeTimes);
// !!请注意上面的三行代码,这是用户代码接受自定义控件库中事件的代码 !
// 上面三行代码分别把用户定义的方法"activeLogin1_GoodLogin","activeLogin1_Cancel"
// 和"activeLogin1_FailedThreeTimes"分别添加到自定义控件中的事件"GoogLogin","Cancel"
// 和"FailedThreeTimes"中; 这样一来只要自定义控件中的对应事件一被激发,这些添加用户自定义方法就会被执行.
// 要注意的是,用户自定义方法签名必须符合对应的多路委托的定义(因为事件是由多路委托
// 定义的,所以要添加到什么事件,定义就必须符合该事件对应的多路委托的定义)
// 而且,这里的Cancel事件类型是系统已经定义的多路委托"EventHandler"类型,所以它的实例化
// 与其它两个事件的实例化有所不同,是"System.EventHandler",而不是"MyEventTEST.EventHandler"!
// 这些用户自定义方法将在下面列出.
// 不过请注意,上面的三行代码虽然在方法InitializeComponent()中,但却是我们自己手工添加的!
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(440, 357);
this.Controls.AddRange(new System.Windows.Forms.Control[] {this.activeLogin1, });
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
// 上面的方法分别是Windows Forms程序的入口.
private void activeLogin1_GoodLogin(object sender, MyEventTEST.LoginEventArgs e)
{
MessageBox.Show("Good Login! " + e.UserID);
}
private void activeLogin1_FailedThreeTimes(object sender, MyEventTEST.LoginEventArgs e)
{
MessageBox.Show("Failed to login three times.");
}
private void activeLogin1_Cancel(object sender, System.EventArgs e)
{
MessageBox.Show("Cancel");
}
// 上面的三个方法(activeLogin1_GoogLogin,activeLogin1_Cancel和activeLogin1_FailedThreeTimes)
// 就是当自定义控件中对应的事件被激发时在当前程序集中对应的处理方法.
// 值得注意的是,签名应该完全符合对应的多路委托的定义!
}
}
文章来源: http://www.jiafangyifang.com/news/newsdata/2003_03_03/3/20030303163532.htm