事件(Event)编程是.Net平台的一大特色,也是.Net倡导的组件编程(Component Programming)的一个重要组成部分,在Windows Forms,ASP.Net,以及众多的异步编程模型中都有重要的应用,对它的深刻理解与把握是.Net平台下软件开发一个重要环节。本文将围绕几个典型的例子详细阐述.Net事件的内部机制,编程模型,为广大程序员提供.Net平台下开发时的考量与学习是的借鉴。
简单的讲,事件就是一种消息通知,它是对象之间传递消息的一种方式。我们日常生活中也有消息通知,单位科长老王打电话来说“小李,今天下午三点开项目洽谈会!”你自然会安排下午的项目洽谈会活动。当然前提是老王和你有这种消息传递的契约关系,即老王有权通知你开项目洽谈会,而你有义务接受这样的通知去开项目洽谈会。这和.Net的事件模型非常类似。.Net采用一种称作“发布——登记——接受”的逻辑来在对象之间传递消息,通知某个事件的发生,如鼠标对某个按钮的点击,电子邮件的到来等等。一个对象只有在发布某事件后才有权力在该事件到来时通知其它对象,而一个对象只有在登记某事件后才有资格在该事件发生后接受发布事件的对象的通知。注意这里发生的事件是在事件发布者对象里发生的,而这一事件又有必要让另一对象知道,这才有这样的事件模型的存在的基础和必要。
在事件模型中,事件发送者起初并不知道哪个对象来接受这样的事件。但又要通知到它,怎么办呢?老王一开始也并不知道要你开会,但它发现自从它安排了项目洽谈会后,你在上面登记过,它通过你的登记电话便找到了你。类似的,事件发送者也要这样一个“登记本”,这在.Net里通过一个称作委派(Delegate)的技术来实现的。委派是指向一个类方法的引用,很类似传统C/C++语言里的函数指针,不过函数指针只能指向一个函数句柄,而委派却可以指向多个方法(这也为事件的多播multicast提供了底层机制),同时增加了普通语言运行时(Common Language Runtime)的类型安全管理。
简单事件模式:
public delegate void MyMultiDelegate(int value);
//事件发布者类
public class Publisher
{
public event MyMultiDelegate handlers; //定义一个事件
//激发事件
public void FireEvent()
{
handlers(10);
}
}
//事件响应者类
public class Subscriber
{
//事件处理函数
public void MyMethod(int i)
{
Console.WriteLine(i);
}
}
class Program
{
static void Main(string[] args)
{
Publisher p = new Publisher();
Subscriber s1 = new Subscriber();
Subscriber s2 = new Subscriber();
//声明为事件的委托 必须使用+=运算符给事件追加委托
p.handlers += s1.MyMethod;
p.handlers += s2.MyMethod;
//声明为事件的委托也不能直接调用,下面这句无法通过编译
p.FireEvent();
Console.ReadKey();
}
}
邮件事件编程案例:
//新邮件事件参数类
public class NewEmailEventArgs : EventArgs
{
public NewEmailEventArgs(string subject, string message)
{
this.subject = subject;
this.message = message;
}
public string Subject
{
get
{
return (subject);
}
}
public string Message
{
get
{
return (message);
}
}
string subject;
string message;
}
//新邮件委派声明
public delegate void NewMailEventHandler(object sender, NewEmailEventArgs e);
//邮件发送类声明
public class EmailSender
{
//新邮件事件声明
public event NewMailEventHandler NewMailEvent;
protected void OnNewMail(NewEmailEventArgs e)
{
if (NewMailEvent != null)
NewMailEvent(this, e);
}
//发送邮件
public void SendMail(string subject, string message)
{
NewEmailEventArgs e = new NewEmailEventArgs(subject, message);
OnNewMail(e);
}
}
//邮件接收者声明
public class EmailReceiver
{
string _name;
public EmailReceiver(string name)
{
_name = name;
}
//处理收到新邮件事件的方法
public void ComeMail(object sender, NewEmailEventArgs e)
{
Console.WriteLine("This is Receiver {0} ,receive a new email:\n{1} {2}", _name,
e.Subject, e.Message);
}
}
//测试类
public class Test
{
public static void Main()
{
EmailSender mySender = new EmailSender();//邮件发送者
EmailReceiver myReceiver1 = new EmailReceiver("Receiver1");//第一个接受者
EmailReceiver myReceiver2 = new EmailReceiver("Receiver2");//第二个接收者
mySender.NewMailEvent += new NewMailEventHandler(myReceiver1.ComeMail);//登记邮件事件
mySender.NewMailEvent += new NewMailEventHandler(myReceiver2.ComeMail); //登记邮件事件
mySender.SendMail("Hello!", "I am from Sender!!!");//发送邮件,多播
mySender.NewMailEvent -= new NewMailEventHandler(myReceiver2.ComeMail); //取消登记邮件事件
mySender.SendMail("Hello!", "I am from Sender!!!");//再次发送邮件,单播
Console.Read();
}
}
在案例1中,
public event MyMultiDelegate handlers; //定义一个事件
这句话,把它修改为
public MyMultiDelegate handlers; //定义一个事件
程序的运行结果,相同。这样子让我想到了,事件机制是不是多余,其实我错了。
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
//挂接事件响应
myCustomButton1.MyClick += new MyClickEventDelegate(myCustomButton1_MyClick);
myCustomButtonUseGenericDelegate1.MyClick += new EventHandler<MyClickEventArgs>(myCustomButtonUseGenericDelegate1_MyClick);
}
void myCustomButtonUseGenericDelegate1_MyClick(object sender, MyClickEventArgs e)
{
MessageBox.Show("I'm clicked " + e.ClickCount.ToString() + " times");
}
void myCustomButton1_MyClick(object sender, MyClickEventArgs e)
{
MessageBox.Show("I'm clicked " + e.ClickCount.ToString() + " times");
}
private void myCustomButton1_Click(object sender, EventArgs e)
{
MessageBox.Show("0");
}
}
public partial class MyCustomButton : Button
{
public MyCustomButton()
{
InitializeComponent();
}
/// <summary>
/// 自定义事件
/// </summary>
public event MyClickEventDelegate MyClick;
/// <summary>
/// 点击次数
/// </summary>
private int ClickCount = 0;
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
ClickCount++;
//激发自定义事件
if (MyClick != null)
MyClick(this, new MyClickEventArgs(ClickCount));
}
}
/// <summary>
/// MyClick事件委托
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void MyClickEventDelegate(Object sender, MyClickEventArgs e);
/// <summary>
/// MyClick事件参数
/// </summary>
public class MyClickEventArgs : EventArgs
{
public int ClickCount = 0; //单击次数
public MyClickEventArgs(int ClickCountValue): base()
{
ClickCount = ClickCountValue;
}
}
这段代码很好的解析了我心中的这个疑惑。哈哈,原来采用事件编程了以后,在调用的时候就直接可以看到该事件的方法。实在是妙啊!