C#编程语言之委托与事件(二)—— C#事件


  前面已经大致讲述了C#委托的一些基础知识点,本文接下来的内容是C#中的事件(Event),在此我提个建议,如果是刚接触C#的委托类型的朋友可以先看到这里,等熟悉了委托的使用之后(大约1-2天)再来了解C#中事件的使用,以免看了以下内容发生思维紊乱的现象。之所以这么说并没有些许自大之意,而相反的是恰恰是认为自己的文章写得结构混乱,不足以明理,如果同时看了委托(delegate)和事件(event)容易知识点混淆,所以只是稍作提醒。

  好了,废话不多说,现在进入正题:

  事件是什么?C#事件是一种具有特殊签名的委托,且在声明事件类型的时候用event关键字修饰。

  说到事件,大家第一个想到的一般都是按钮点击事件,既然说到按钮点击事件,下面就着按钮事件作为例子说开去。

  首先,在Winform界面拖拽一个按钮,双击按钮,代码会自动定位到按钮点击函数,右击点击事件的函数名,查找所有引用,可以看到总共包括以下两处引用:

private void button1_Click(object sender, EventArgs e) {}
this.button1.Click += new System.EventHandler(this.button1_Click);

  在上面为什么我要特别提到定位到第一个语句是一个函数,而不是事件,因为真正的时间是那个 button1.Click 而不是那个 button1_Click ,因为经常听到有人提到说“双击按钮定位到按钮点击事件”,个人感觉这个说法对事件的概念有失偏颇,顺带地提一提。

  在此可以看到函数的参数列表(object sender, EventArgs e),这个参数列表必须是确定的,一个表示点击事件的执行者,一个表示事件参数类,不局限于object和EventArgs,但必须是这个两个类的子类或本身。

  下面看一下创建一个事件的步骤:1

  (1)、定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象

  (2)、定义事件参数类,此类应当从System.EventArgs类派生

  (3)、定义事件处理方法,此方法符合步骤(1)定义的委托签名

  (4)、用event关键字定义事件对象,它同时也是一个delegate对象

  (5)、用+=操作符添加事件到事件队列当中,-=操作符把事件从事件队列中删除

  (6)、在需要触发事件的地方调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。

  下面我们通过模拟一个Button点击事件来深入了解:

 1 using System;
 2 namespace ConsoleApplication {
 3     //(1)定义一个delegate对象类型
 4     delegate void myEventHandle(object sender, EventArgs e);
 5     class MyEventArg : EventArgs {
 6         //(2)定义事件类,应派生于EventArgs类
 7         public string EventArgsName() {
 8             return "[MyEventArgsName]";
 9         }
10     }
11     class _Button {
12         //(4)定义事件对象,注意关键字和类型
13         public event myEventHandle Click;
14         protected void OnUtilEvent(MyEventArg  e) {
15             if(null != Click) {
16                 Click(this, e);
17             }
18         }
19         public void RaiseEvent() {
20             MyEventArg e = new MyEventArg();
21             OnUtilEvent(e);
22         }
23         public string ButtonName() {
24             return "[myButtonName]";
25         }
26     }
27     class EventTest {
28         //(3)定义事件处理方法,保证函数列表和返回值的有效性
29         static void _Button_Click(object sender, EventArgs e) {
30             Console.WriteLine("\nYou have clicked the button");
31             Console.WriteLine("The eventObject is :\t"+(sender as _Button).ButtonName());
32             Console.WriteLine("The eventArgs is:\t"+(e as MyEventArg).EventArgsName());
33         }
34         static void Main() {
35             _Button _button = new _Button();
36             //(5)用操作符把事件添加到事件队列当中
37             _button.Click += new myEventHandle(_Button_Click);
38             Console.Write("Do you want to click the button(Y/N)");
39             if (Console.ReadKey().KeyChar == 'Y')
40                 _button.RaiseEvent();
41             else 
42                 Console.WriteLine("The event does not raise");
43         }
44     }
45 }

  其中我已经把创建步骤写成注释表明在代码中了,模拟鼠标点击事件我们修改为按下“Y”触发,当看到 _button.Click += new myEventHandle(_Button_Click); 有没有恍然大悟的感觉。其实原理并不难,多练习几次条理就清晰。书读百遍,其义自现,说的亦是如此。

  使用某IDE编写以上例子的时候可能在 if(null != Click){ Click(this,e); } 这段语句时会报一个[NOTICE],提示该表示方法可以简单化,可以改成 Click?.Invoke(this, e); 注意这不是错误,如此表示只是为了降低理解难度,其实两个语句的作用是一样的,判断Click对象是否为null,然后执行该事件。

  事件发布器:

  通过一个例子来直接了解事件发布

 1 using System;
 2 namespace ConsoleApplication {
 3     public delegate void myEventHandle(object sender,FoodEventArg e);
 4     public class FoodEventArg : EventArgs {
 5         private string food;
 6         public FoodEventArg(string food) {
 7             this.food = food;
 8         }
 9         public string getFood() {
10             return food;
11         }
12     }
13     class Person {
14         private string name;
15         public Person(string name) {
16             this.name = name;
17         }
18         public void FoodRecive(object sender,FoodEventArg e) {
19             Console.WriteLine("{0} receives {1}",name,e.getFood());
20         }
21     } 
22     class Dealer {
23         public event myEventHandle FoodInfo;
24         public void Receive(string food) {
25             Console.WriteLine("Dealer: food->{0}",food);
26             RaiseEvent(food);
27         }
28         protected void RaiseEvent(string food) {
29             FoodInfo?.Invoke(this, new FoodEventArg(food));
30         }
31     }
32     class EventTest {
33         static void Main() {
34             Dealer _dealer = new Dealer();
35             Person _personA = new Person("Evan");
36             Person _personB = new Person("Cathy");
37             _dealer.FoodInfo += _personA.FoodRecive;
38             _dealer.Receive("Rice");
39             _dealer.FoodInfo += _personB.FoodRecive;
40             _dealer.Receive("Potato");
41             _dealer.FoodInfo -= _personA.FoodRecive;
42             _dealer.Receive("Cucumber");
43         }
44     }
45 }

  使用Dealer的FoodInfo事件,通过“+=”来注册一个订阅(实质上就是把一个事件添加到事件队列当中),通过“-=”来取消一个订阅。

  例子中食物(Food)的接收一个事件参数,当一个食物被接收时,查看有多少用户订阅该事件可以接收到该食物,一开始只有Even订阅了事件,所以Even接收到了Rice,然后Cathy注册了订阅,所以Even和Cathy(队列结构)依次接收到了Potato,随后Even取消了订阅,所以后来的Cucumber只有当前注册订阅用户Cathy能够接收。

  懒得手写运行结果,以下贴出运行结果图:

  通过这个例子能够比较清楚地明白C#事件当中 事件执行 者和 事件参数 的作用,C#事件是一种新的类型处理方式,同时更是一种设计模式。

  声明:以上 C#编程语言之委托与事件(一)和 C#编程语言之委托与事件(二)均由本人原创编写,文中的每一处代码均经过本人亲自测试能够按照文中所述的结果呈现,如果问题,请及时告知。

  文章引用:

  1、http://www.cnblogs.com/xlx0210/archive/2010/08/08/1794959.html

  尊重知识产权,争取共同进步!