4.2 事件(1)
任何编写过图形用户界面(GUI)软件的开发人员都熟悉事件处理编程,当用户与GUI控制进行交互时(例如单击表格上的按钮),作为上述事件的反应,就会执行一个或多个方法。没有用户的参与,事件也可能执行。其实各种技术框架下都会有"事件"这一名词,并且所有的定义都基本相同。在.NET中,事件和委托在本质上并没有太大的差异,但在.NET实际环境下,事件的运用比委托更加广泛,本节将集中介绍有关事件的面试题。
面试题52 什么是事件机制
事件的重要性不仅在.NET程序的设计中有着非常重要的地位,首先事件这个概念在各种技术框架中都有很重要的地位,其次是事件的定义非常符合逻辑世界的概念。所以在.NET面试中,关于事件的考题必然会频繁出现。
【出现频率】★★★★★
【关键考点】
事件的概念
事件应用
【考题分析】
事件是.NET程序员中经常使用的一个机制,在.NET中,事件的定义和其他技术框架下的事件处理基本一致。代码如下: - public delegate void MyEventHandler(object sender, MyEventArgs e);
下面的代码展示了一个控制台输出和使用的示例。示例代码如下:
- public class ctrEvent
- {
- //定义一个委托
- public delegate void SomeHandler(object
sender, System.EventArgs e); - public event SomeHandler SomeEvent;
//定义一个委托事件 - public ctrEvent()
- {
- this.SomeEvent += new SomeHandler(this.
ProcessSomeEvent); - //事件绑定
- }
- public void RaiseSomeEvent()
- {
- EventArgs e = new EventArgs();
- Console.WriteLine("请输入'Andy'");
- string s = Console.ReadLine();
- if (s.ToLower() == "andy")
- {
- //请在用户输入一个andy的情况下触发事件,否则不触发
- SomeEvent(this,e);
- }
- else
- {
- Console.WriteLine("错误的输入");
- }
- }
- private void ProcessSomeEvent(object sender, EventArgs e)
- {
- Console.WriteLine("你好");
- }
- }
为了演示事件的响应,以下示例代码定义了一个ResponseSomeEvent()方法。当控制台事件被触发发生时,该类型对象将向控制台输出的内容。 - using System;
- namespace MyConsole
- {
- //事件的接收者(事件的响应)
- class Container
- {
- private ctrEvent ctrEvent1 = new ctrEvent(); //初始化ctrEvent对象
- public Container()
- {
- //将事件绑定到相应方法ResponseSomeEvent()
- ctrEvent1.SomeEvent += new ctrEvent.SomeHandler(this.Response
- SomeEvent);
- //调用RaiseSomeEvent()方法,当输入正确的口令就会出发事件
- ctrEvent1.RaiseSomeEvent();
- }
- public static void Main()
- {
- Container pane = new Container(); //初始化Container
- Console.ReadLine();
- }
- //接收者对事件的响应
- private void ResponseSomeEvent(object sender, EventArgs e)
- {
- Console.WriteLine("事件响应,也就是事件发生了");
- }
- }
- }
代码执行结果如下: - 请输入'Andy'
- Andy
- 你好
- 事件响应,也就是事件发生了
【答案】
事件是.NET程序员中经常使用的一个机制,事件是一种使对象或类能够提供通知的成员,客户端可以通过提供事件处理程序为相应的事件添加可执行代码,事件可以理解为一种特殊的委托。
4.2 事件(2)
面试题53 列举一个委托和事件的实例
在前面的章节中,笔者已经分别展示了委托与事件的使用方法。事件在本质上依托于委托的机制,在本小节中,笔者将借助一个具体的实例分析事件和委托的联系,帮助读者理解这两个重要的概念。
【出现频率】★★★★
【关键考点】
事件
委托
【考题分析】
读者可能已经发现,事件的定义和使用方式和委托极其类似,那两者是何关系呢?事实上,事件本身就是一个委托类型。关于这个问题,举一个具体的例子,比如说一个公司(场景),笔者是老板,手下有两个员工,小赵和小李。你命令小赵,如果小李玩游戏,则小赵扣去小李400元钱。这就是现实中的委托。实际上,在写程序中,程序员就是老板,小赵和小李就是两个对象。小李玩游戏是一个方法,小李还有一个游戏事件,小李玩游戏激发这个事件。而小赵就是事件处理对象,小赵负责把小李的钱扣除400。
所以,委托有如下几个要素:
激发事件的对象:就是小李。
处理对象事件的对象:就是小赵。
定义委托:就是你让小赵监视小李。
如果这3个要素都满足,则就写出了一个完整事件的处理。首先编写小赵这个对象,代码如下: - //负责扣钱的人
- public class Zhao
- {
- public Zhao()
- {
- Console.WriteLine("生成小赵");
- }
- public void DeductMoney(object sender, EventArgs e)
- {
- Console.WriteLine("小赵:好小子,上班时间胆敢玩游戏...");
- Console.WriteLine("小赵:你不知道上班打游戏要罚款吗?);
- Li f = (Li)sender; //发送事件
- Console.WriteLine("小李的工资: " + f.Money.ToString());
- Console.WriteLine("开始扣钱...");
- System.Threading.Thread.Sleep(500); //延迟
- ff.Money= f.Money- 400; //扣除工资
- Console.WriteLine("扣完了...现在小李还
剩下:" + f.Money.ToString()); - }
- }
接着创建小李这个对象,代码如下: - //如果玩游戏,则引发事件
- public class Li
- {
- //先定义一个事件,这个事件表示小李在玩游戏
- public event PlayGameHandler PlayGame;
- //保存小李钱的变量
- private int m_Money;
- public Li()
- {
- Console.WriteLine("生成小李...");
- m_Money = 2000;
//构造函数,初始化小李的钱 - }
- public int Money //此属性可以操作小李的钱
- {
- get
- {
- return m_Money;
- }
- set
- {
- m_Money = value;
- }
- }
- public void Game()
- {
- Console.WriteLine("小李开始Game了...");
- Console.WriteLine("小李:魔兽好玩,哈哈哈! 我玩...");
- System.Threading.Thread.Sleep(500); //延迟
- System.EventArgs e = new EventArgs(); //创建新事件
- //OnPlayGame(e);
- if (PlayGame != null)
- {
- PlayGame(this, e);
- }
- }
- protected virtual void OnPlayGame(EventArgs e)
- //重写
基类的OnPlayGame()方法 - {
- if (PlayGame != null)
- {
- PlayGame(this, e);
- }
- }
- }
当这两个对象建立完成后,运行主程序触发事件,代码如下: - namespace MyConsole
- {
- //定义委托处理程序
- public delegate void PlayGameHandler(object
sender, System.EventArgs e); - public class BackgroundEvent
- {
- [STAThread]
- public static void Main(string[] args)
- {
- Console.WriteLine("场景开始了...");
- //生成Zhao
- Zhao z = new Zhao();
- //生成Li
- Li l = new Li();
- l.PlayGame += new PlayGameHandler(z.
DeductMoney); //指定监视 - l.Game();
//开始Game - Console.WriteLine("场景结束...");
- Console.ReadLine();
- }
- }
- }
运行结果如下: - 场景开始了...
- 生成小赵
- 生成小李...
- 小李开始Game了...
- 小李:魔兽好玩,哈哈哈! 我玩...
- 小赵:好小子,上班时间胆敢玩游戏...
- 小赵:你不知道上班打游戏要罚款吗?
- 小李的工资:2000
- 开始扣钱...
- 扣完了...现在小李还剩下1600
- 场景结束...
【答案】
正如本小节代码所示,程序员定义了一个事件时,事实上是定义了一个特定的委托成员。该委托没有返回值,并且拥有两个参数:object sender和EventArgs e。而当事件使用者订阅事件时,本质上就是把事件处理方法加入委托链表之中。希望读者认真地阅读理解本小节的代码实例,在实际的开发设计中能更好地运用事件以及委托的特性。
4.2 事件(3)
面试题54 请简述EventHandlerList的作用
事件触发在程序开发过程中随处可见,尤其是在有用户交互的界面程序中更加突出。这些交互界面中往往包含多个事件,如果为每个事件都添加一个事件成员,不仅导致程序臃肿而且每个类型对象都将分配一定的系统内存。本小节将介绍如何解决这类问题。
【出现频率】★★★★★
【关键考点】
EventHandlerList的作用
EventHandlerList示例
【考题分析】
程序员在程序开发的过程中经常碰见在一个类中实现多个事件属性的需求,要求类必须在内部存储和维护每个事件定义的委托。通常的一种做法方法是通过事件进行索引的委托集合。
但若要存储每个事件的委托,笔者推荐使用EventHandlerList类或实现自己的集合。但需要注意的是,该集合类必须提供用基于事件键设置、访问和检索事件处理程序委托的方法。
下面将说明如何使用一个EventHandlerList成员来存储所有的事件,类中的每个事件属性定义一个Add 访问器方法和一个Remove 访问器方法。事件属性的Add 访问器将输入委托实例添加到委托集合。事件属性的Remove 访问器从委托集合中移除输入委托实例。如以下示例代码所示: - // 类MyControl定义了两个事件属性,MouseUp和MouseDown
- class MyControl: Component {
- // 定义其他控制方法和属性
-
- protected EventHandlerList listEventDelegates
= new EventHandlerList(); // 定义委托的集合 - static readonly object mouseDownEventKey = new object();
-
// 为每一个活动的独特的关键 - static readonly object mouseUpEventKey = new object();
-
- //定义MouseDown事件属性
- public event MouseEventHandler MouseDown {
- // 输入委托添加到集合
- add { listEventDelegates.AddHandler
(mouseDownEventKey, value); } - // 从集合中删除输入的委托
- remove { listEventDelegates.RemoveHandler
(mouseDownEventKey, - value); }
- }
- // MouseUp事件定义属性
- public event MouseEventHandler MouseUp {
- //输入委托添加到集合
- add { listEventDelegates.AddHandler(mouseUpEventKey, value); }
- // 从集合中删除输入的委托
- remove { listEventDelegates.RemoveHandler
(mouseUpEventKey, value); } - }
- }
注意:可以使用EventHandlerList类或实现自定义的集合。例如可以使用Hashtable类或从 DictionaryBase类派生一个自定义类,不需要在类以外公开委托集合的实现详细信息。
【答案】
EventHandlerList最主要的作用就是提供一个存储事件的集合,程序员可以方便地将某个类型中多个事件维护在EventHandlerList集合中,而不需要独立的维护每一个事件。
|