万物皆对象!对象与对象之间不仅仅是存在关系,更是具有千丝万缕的联系。即:如何使得一个对象发生变化时,另一个对象也能随之发生变化。这种“一个对象的状态改变导致其它对象状态改变”的现象,我们称之为事件。
定义:事件,是用来描述类的成员发生改变的对象。
我们举一个例子,来理解上面的定义:
例如:路边有一条狗。你看它不爽,于是走上前去踢了它一脚。我们来看紧接着发生的事情:1、这条狗很胆小,它跑掉了;2、这条狗是恶狗,它将你反咬一口;3、这条狗身体瘦弱,经不起你的一脚,一命呜呼了;4、……我想说明的是:在你真正踢它之前,你无法知道它会干些什么!在这件事发生之前,这条狗也无法预计你会在什么时候踢它,它唯一能做的就是“等着被踢”!于是,在封装 Dog 类型的时候,我们不能确定 Dog 类型的实例被踢的时机,同时不能确定被踢之后做些什么!同理,微软工程师们不论有多么牛,他们也不能在封装 Button 类的时候确定,你“将”对这个 Button 什么时候,干些什么!使用委托,我们可以解决这个问题。解决问题的思路是:在封装 Button 的时候声明一个委托的引用,但是不实例化这个委托,却又在用户单击 Button 的时候调用这个委托。这样一来,他们的用户(Button 的使用者),就可以通过首先实例化这个委托的方式来指定当委托被调用时需要执行的方法了。这样理解起来可能比较复杂,我们来看一个简单的例子,然后推测 Button 是如何工作的。首先回到刚才的踢狗事件中。我们来封装一个 Dog 类。
///
// 代码片断1 /// public class Dog { /// <summary> /// 描述狗被踢的过程 /// </summary> public void Kicked() { //无法确定被踢时需要做些什么。 } }紧接着我们声明一个事件成员(实际上是某个委托的一个引用),在被踢时调用这个委托的引用,用于表示被踢的这个瞬间狗的状态改变。事先,应该有一个委托: public delegate void KickedEventHandler(); 使用这个委托,我们在 Dog 类中声明“踢”的事件。并且在描述狗被踢的方法中引发该事件(也就是调用委托),以描述狗在这个瞬间状态发生了变化。///
// 代码片断2 /// public class Dog { /// <summary> /// 踢事件。实际上应该是“被踢”事件。 /// 正如 Button 的单击事件,实际上应该是“被单击”事件 /// </summary> public event KickedEventHandler Kick; // 代码1) /// <summary> /// 描述狗被踢的过程 /// </summary> public void Kicked() { this.Kick(); // 代码2) } }上面的代码,有以下内容需要大家理解:1、代码1)处,是声明事件成员的语法。如果不看关键字 event ,你会发现:声明类的事件成员,实际上是声明了一个委托类型的引用(变量)。这里,我们声明了一个类型是 KickedEventHandler 的委托类型的变量名叫 Kick。2、引用 Kick 尚未实例化。这个实例化的过程,我们将在后面的“订阅事件”的步骤中进行。订阅事件的时候才确定这个委托调用什么函数。3、代码2)处,直接调用 Kick 委托(或者说叫做触发了事件 Kick)。此时此刻,对于编写 Dog 类的我们而言,不能确定使用 Dog 类的人想调用什么方法。以上代码,我们完成了事件编写的两个步骤:
1、声明事件2、调用事件 紧接着,我们来看看这个 Dog 类怎么使用。首先编写一个 Main() 函数,调用 Dog 类。 /// // 代码片断3 /// class Test { static void Main(string[] args) { Dog dog = new Dog(); } } 我们实例化了“一条狗”。为了能够使狗在被踢的时候做出响应,我们需要和这条狗有一个约定:当我踢它时,它应该有点反应。这个过程就是“订阅事件”。一旦订阅了事件,我们和狗就好像有了这个约定。所有其它未订阅事件的狗,将不会响应我们的“踢”的动作。 /// // 代码片断4 /// class Test { static void Main(string[] args) { Dog dog = new Dog(); dog.Kick += new KickedEventHandler(dog_Kick); // 代码1) } private static void dog_Kick() { } } 上面的代码,有以下说明:1、代码1)处,从语法上说是实例化了一个 KickedEventHandler 委托类型的对象。回顾委托的运行机制,这行代码决不是立即调用 dog_Kick() 方法。2、代码1)处,使用了委托的引用 dog.Kick 指向刚刚实例化的委托对象 new KickedEventHandler(dog_Kick)。回顾委托的运行机制,这行代码的实际含义是:在将来调用 Kick 这个委托的时候,编译器执行 dog_Kick 方法。那么 Kick 委托在什么时候调用的呢?请看代码片断2的代码2)处的代码行。原来,我们只要一调用 Dog 类的 Kicked() 方法,就会调用委托 Kick (触发事件实际上就是调用委托),一旦调用委托,由于先前有“在将来调用 Kick 这个委托的时候,编译器执行 dog_Kick 方法。”的约定,则方法 dog_Kick() 就会执行。 因此,最后一步,我们再具体实现 dog_Kick() 方法的方法体,就可以将代码在 dog.Kick 事件触发时运行了。 /// // 代码片断5 /// class Test { static void Main(string[] args) { Dog dog = new Dog(); dog.Kick += new KickedEventHandler(dog_Kick); dog.Kicked(); } private static void dog_Kick() { Console.WriteLine("狗跑掉了"); } } /// 以上代码,我们完成了事件编写的后两个步骤: 3、订阅事件 4、实现事件处理函数 最后,我们来看看,是否达到了本文最初所需的效果: 首先,通览本文示例的代码: /// // 代码片断6 /// /// <summary> /// 在 Kick 事件中使用的委托 /// </summary> public delegate void KickedEventHandler(); /// <summary> /// 描述狗。为了简单起见,没有声明任何有效的类成员。 /// </summary> public class Dog { /// <summary> /// 踢事件。实际上应该是“被踢”事件。 /// 正如 Button 的单击事件,实际上应该是“被单击”事件 /// </summary> public event KickedEventHandler Kick; /// <summary> /// 描述狗被踢的过程 /// </summary> public void Kicked() { this.Kick(); } } /// <summary> /// 测试 Dog 类的主类。 /// </summary> class Test { /// <summary> /// 应用程序的入口点 /// </summary> /// <param name="args">由控制台传入的主函数参数</param> static void Main(string[] args) { Dog dog = new Dog(); // 订阅事件 dog.Kick += new KickedEventHandler(dog_Kick); // 调用 Kicked() 函数,踢狗。 dog.Kicked(); } /// <summary> /// 被订阅到 Kick 事件的事件处理程序. /// 这个函数没有使用 obj.dog_Kick() 的语法调用,但是同样执行了。 /// 这是委托的功劳,由此可见委托在事件机制中的作用。 /// </summary> private static void dog_Kick() { Console.WriteLine("狗跑掉了"); } } /// 运行上面的代码,你会发现:我们通过调用 Dog 中的 Kicked() 方法,导致 Test 类中的 dog_Kick() 方法被执行。这是不是就是“一个对象的状态改变导致其它对象状态改变”的效果呢? 同理,可以很大胆的推测 Button 类中必定包含这样的代码。请读者自行分析。 /// // 代码片断6 // 说明:该代码片断只是对事件执行过程的说明,.Net Framework 中的 System.Windows.Forms.Button 类的封装实际上要复杂的多。 /// public class EventArgs { //这个类描述事件参数对象... } public delegate void EventHandler(object sender, EventArgs e); public class Button { public event EventHandler Click; /// <summary> /// 描述按钮被点击的过程 /// </summary> public virtual void PerformClick() { // 第一个参数传入 this, 则在 Click 的事件处理程序中可以获得当前被单击的按钮对象。 // 这个使用事件传递参数的过程,可以使得事件处理程序中的形参不需实例化就可使用,这个过程叫做事件的参数“回调”。 this.Click(this, new EventArgs()); } } public class TestForm { public TestForm() { Button button = new Button(); button.Click += new EventHandler(button_Click); // 此处没有直接调用 PerformClick() 方法。那是因为按钮的单击事件通常都是用鼠标或其它输入设备触发的。 // 当然了,只要您愿意,也完全可以调用 button.PerformClick() 以触发按钮的单击事件。请读者自行编码试试。 // button.PerformClick(); } private void button_Click(object sender, EventArgs e) { // 输出被单击的按钮上显示的文本 MessageBox.Show( ((Button)sender).Text ); } static void Main(string[] args) { Application.Run(new TestForm()); } } ///