前面简要介绍了委托的基本知识,包括委托的概念、匿名方法、Lambda表达式等,现在讲讲与委托相关的另一个概念:事件。

事件由委托定义,因为事件的触发方(或者说发布方)并不知道事件的订阅方会用什么样的函数名称,这个函数名称由订阅方自己决定。假如不这样做,那么事件的订阅方必须公开一个专门用于处理事件的函数给事件触发方,由触发方在事件触发的时候调用这个函数。这样一来,触发方必须知道订阅方的细节,才能有效地触发事件,显然这是不合理的,触发方与订阅方耦合性太大了,不具备通用性。

事实上,事件的触发方只需要确定好事件处理函数的签名即可。也就是说,触发方只需要定义在事件发生时需要传递的参数,而在订阅方,只需要根据这个签名定义一个处理函数,然后将该函数“绑定”到事件列表,就可以通过签名中的参数,对事件做相应的处理。定义函数签名非常简单,就是使用委托。下面我们来简单看一个例子。这个例子模拟一个服务器程序,它有Start和Stop两个操作,分别表示启动和停止服务。在成功启动以及成功停止时,都会触发一个“成功”的事件,并公布事件发生的确切时间。

一、定义函数签名(委托)

其实对于我们的例子,事件处理函数的签名中只需要一个参数,就是事件发生的确切时间。因此在定义委托的时候,只需要定义一个时间(DateTime)类型的参数即可。为了能够让我们的程序看上去更加标准,并且为了后面描述的方便,我们还是将这个参数封装在一个类里,并且该类继承于System.EventArgs类。

  1. public class ServerEventArgs : System.EventArgs
  2. {
  3. #region Public Properties
  4. /// <summary>
  5. /// 读取或设置服务器事件发生的时间
  6. /// </summary>
  7. public DateTime FireDateTime { get; set; }
  8. #endregion
  9. #region Constructors
  10. /// <summary>
  11. /// 默认构造函数,使用当前时间作为服务器事件
  12. /// 发生的时间
  13. /// </summary>
  14. public ServerEventArgs()
  15. {
  16. this.FireDateTime = DateTime.Now;
  17. }
  18. /// <summary>
  19. /// 使用给定的事件作为服务器事件发生的时间并
  20. /// 对参数对象进行初始化
  21. /// </summary>
  22. /// <param name="fireDateTime"></param>
  23. public ServerEventArgs(DateTime fireDateTime)
  24. {
  25. this.FireDateTime = fireDateTime;
  26. }
  27. #endregion
  28. }

现在来定义这个委托:

  1. public delegate void ServerEventHandler(object sender, ServerEventArgs e);

委托的第一个参数表示事件将由谁来触发(谁是事件的发布者),而第二个参数则是我们刚刚定义的事件参数,它只有一个属性,就是事件的触发时间。函数签名(委托)已经定义好了,接下来需要对事件进行定义。

二、定义事件

在C#中,使用event关键字定义事件。事件定义的形式是:“<modifier> event <event_handler> name”,其中modifier是大家熟知的访问修饰符,也就是“public”、“protected”等,event_handler是定义了事件处理函数签名的委托(在本例中,也就是上面的ServerEventHandler),name自然就是这个事件的名称了。由此,我们的两个事件(成功启动事件和成功停止事件)可以定义如下:

  1. /// <summary>
  2. /// 定义一个事件,当服务器正常启动后,触发该事件
  3. /// </summary>
  4. public event ServerEventHandler Started;
  5. /// <summary>
  6. /// 定义一个事件,当服务器正常结束后,触发该事件
  7. /// </summary>
  8. public event ServerEventHandler Stopped;

三、触发事件

事件当然由其发布者触发。考察服务器“Server”这个对象,启动和停止是其本身应有的操作,因此,启动与停止是否成功,也就只有它自己知道。那么成功启动与成功停止的事件自然由其自身引发。事件的触发可以在对象中的任何地方发生,比如在本例中,我们可以在Start方法最后部分调用Started事件,而在Stop方法的最后部分调用Stopped事件。从扩展性方面考虑,我们还是把事件的触发单独放到一个protected方法中,这样做的好处是,当我们对“Server”进行扩展的时候,我们还可以重写这个protected方法,以便在事件触发之前再进行其它特殊的操作。因此,我们的事件触发部分就实现如下:

  1. protected virtual void DoStarted(object sender, ServerEventArgs e)
  2. {
  3. if (Started != null)
  4. Started(sender, e);
  5. }
  6. protected virtual void DoStopped(object sender, ServerEventArgs e)
  7. {
  8. if (Stopped != null)
  9. Stopped(sender, e);
  10. }
  11. /// <summary>
  12. /// 执行服务器的启动操作
  13. /// </summary>
  14. public void Start()
  15. {
  16. // TODO: 在此启动服务器
  17. DoStarted(this, new ServerEventArgs(DateTime.Now));
  18. }
  19. /// <summary>
  20. /// 执行服务器的停止操作
  21. /// </summary>
  22. public void Stop()
  23. {
  24. // TODO: 在此停止服务器
  25. DoStopped(this, new ServerEventArgs(DateTime.Now));
  26. }

由此,服务器在成功启动后,就会调用DoStarted方法,进而触发Started事件;服务器的停止操作也与此类似。我们需要注意到DoStarted和DoStopped方法中的条件判断语句,该语句是用来检查Started和Stopped事件列表中是否有订阅,如果有则触发事件。这种做法是很有必要的,因为并非所有的访问者都会去订阅事件。

四、订阅事件

在订阅者内订阅事件,只需要将事件处理函数添加到相应的事件中即可。在C#中使用“+=”运算符将事件处理函数添加到事件,而使用“-=”运算符将事件处理函数从事件中删除。下面的代码初始化了一个服务器,订阅了该服务器的成功启动与成功停止事件,并试图启动和停止服务。我们可以看到,在服务器成功启动和成功停止完成后,系统会输出启动或停止的具体时间。

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Server server = new Server();
  6. // 订阅成功启动的事件
  7. server.Started += new Server.ServerEventHandler(server_Started);
  8. // 订阅成功停止的事件
  9. server.Stopped += new Server.ServerEventHandler(server_Stopped);
  10. // 启动服务
  11. server.Start();
  12. // 休息3秒钟,以模拟服务的处理时间
  13. Thread.Sleep(3000);
  14. // 停止服务
  15. server.Stop();
  16. }
  17. static void server_Stopped(object sender, ServerEventArgs e)
  18. {
  19. Console.WriteLine("Server successfully stopped at: {0}", e.FireDateTime);
  20. }
  21. static void server_Started(object sender, ServerEventArgs e)
  22. {
  23. Console.WriteLine("Server successfully started at: {0}", e.FireDateTime);
  24. }
  25. }

本文简要介绍了事件的概念、事件的定义、触发以及订阅的相关内容。有关事件的其它内容,比如EventHandler和EventHandler<T>委托、事件处理函数列表、接口内事件的实现等,将在后续的文章中一一介绍。

本文相关演示源代码:点击下载此文件

C#基础:事件(一) 【转】的更多相关文章

  1. 【深入浅出Linux网络编程】 “基础 -- 事件触发机制”

    回顾一下“"开篇 -- 知其然,知其所以然"”中的两段代码,第一段虽然只使用1个线程但却也只能处理一个socket,第二段虽然能处理成百上千个socket但却需要创建同等数量的线程 ...

  2. 第一百六十九节,jQuery,基础事件

    jQuery,基础事件 学习要点: 1.绑定事件 2.简写事件 3.复合事件 JavaScript 有一个非常重要的功能,就是事件驱动.当页面完全加载后,用户通过鼠标 或键盘触发页面中绑定事件的元素即 ...

  3. C#基础---事件的使用

    一:什么是事件     事件是可以被控件识别的操作,如按下确定按钮,选择某个单选按钮或者复选框.每一种控件有自己可以识别的事件,如窗体的加载.单击.双击等事件,编辑框(文本框)的文本改变事件,等等.事 ...

  4. 9、网页制作Dreamweaver(jQuery基础:事件)

    事件 定义 即当HTML中发生某些事(点击.鼠标移过等)的时候调用的方法 $(selector).action() 触发 事件的触发有两种方法: 1.直接将事件click写在<javascrip ...

  5. JS基础——事件绑定

    上一篇博客JS事件对象中,老师问JS事件处理和VB中的事件处理有什么联系?先来解决一下这个问题.举个VB.net中事件处理的样例(JS敲久了,VB习惯的都不熟悉了,看来得常常回想了): 1.事件处理V ...

  6. javascript基础-事件1

    原理 事件分两种.第一种浏览器事件,由浏览器抛出事件,它是人机交互的基础:第二种自定义事件,由程序员抛出事件,它是模拟事件流程.两者都是为了完成数据的传递. 浏览器事件 机制 冒泡和捕获两种机制.因I ...

  7. javascript基础-事件2

    DOM0,DOM2,DOM3事件类型 图解: 范畴 响应顺序(标:标准浏览器.IE9+) 注意点 MouseEvent 标: mousedown-mouseup-click-mousedown-mou ...

  8. jQuery事件篇---基础事件

    写在前面: 有一段时间未更新博客了,利用这段时间,重新看了<jQuery基础教程 第四版>和<锋利的jQuery 第二版>,这两本书绝对是jQuery入门非常好的书,值得多读几 ...

  9. C#基础-事件 继承类无法直接引发基类的事件

    An event can be raised only from the declaration space in which it is declared. Therefore, a class c ...

  10. jquery基础事件

    一.常用的事件有:click.dblclick. mousedown.mouseup.mousemove.mouseover.mouseout.change.select.submit.keydown ...

随机推荐

  1. html5 laboratory - drawing in the canvas

    html5 laboratory - drawing in the canvas Creating a bar chart with canvas 21st February 2010 The exp ...

  2. hdu 5610 Baby Ming and Weight lifting

    Problem Description Baby Ming is fond of weight lifting. He has a barbell pole(the weight of which c ...

  3. Struts2使用Interceptor实现权限控制的应用实例详解

    Struts2使用Interceptor实现权限控制的应用实例详解 拦截器:是Struts2框架的核心,重点之重.因此,对于我们要向彻底学好Struts2.0.读源码和使用拦截器是必不可少的.少说了. ...

  4. &lt;源代码&gt;FTPclient追加方式上传自己定义信息

    实现功能:向FTPserver以追加方式上传自己定义信息(例程中为:2014-10-08 13:47:15 test.) 源代码下载(免积分):http://download.csdn.net/det ...

  5. struts2的<constant/>标签使用

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-/ ...

  6. Mock.js:前后端分离开发工具

    概述 Mock.js实现的功能 基于 数据模板 生成数据 基于 HTML模板 生成数据 拦截并模拟 Ajax请求 用法 浏览器: <!-- (必选)加载 Mock --> <scri ...

  7. session之退出登陆

    <span style="font-size:32px;">//使用SESSION必须先开启session session_start(); //彻底删除session ...

  8. 未找到具有固定名称“System.Data.SQLite”的 ADO.NET 提供程序的实体框架提供程序

    用户代码未处理 System.InvalidOperationException   HResult=-2146233079   Message=未找到具有固定名称"System.Data. ...

  9. WCF方式调用asmx设置cookie

    以前旧的方式去调用web service, 可以通过CookieContainer去设置cookie,改用WCF方式去调用,则必须配置allowCookies属性为true <system.se ...

  10. iOS二进制和资源包的自检

    因为现在做的是企业版的APP,不能保证用户不越狱,也不能保证有些用户喜欢自己修改ipa包里的资源文件,比如更换一些资源图片.因此,我们有必要在请求报文中,增加正版应用的二进制和资源文件的标识,让服务器 ...