前面简要介绍了委托的基本知识,包括委托的概念、匿名方法、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. category和extensions

    catgory 允许你为一个已经存在的类增加方法,而不需要增加一个子类.而且不需要知道它内部具体的实现. 另外,虽然Category不能够为类添加新的成员变量,但是Category包含类的所有成员变量 ...

  2. Android学习总结——实现Home键功能

    实现Home键功能简而言之就是回到桌面,让Activity不销毁,程序后台运行. 实现方法: Intent intent= new Intent(Intent.ACTION_MAIN); intent ...

  3. 一笔画问题(floyd+oular+dfs)

    一笔画问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:4   描述 zyc从小就比较喜欢玩一些小游戏,其中就包括画一笔画,他想请你帮他写一个程序,判断一个图是否能够用一笔画下 ...

  4. wso2esb源码编译总结

    最近花了两周的空闲时间帮朋友把wso2esb的4.0.3.4.6.0.4.7.0三个版本从源码编译出来了.以下是大概的一些体会. wso2esb是基于carbon的.carbon是个基于eclipse ...

  5. [KMP求最小循环节][HDU1358][Period]

    题意 求所有循环次数大于1的前缀 的最大循环次数和前缀位置 解法 直接用KMP求最小循环节 当满足i%(i-next[i])&&next[i]!=0 前缀循环次数大于1 最小循环节是i ...

  6. 批处理Bat实现整合定时关机或取消定时关机

    @echo off :start choice /c:12 /m "输入1为设置定时关机,2为取消定时关机: " if errorlevel 2 goto cancel if er ...

  7. EF MySQL 提示 Specified key was too long; max key length is 767 bytes错误

    在用EF的CodeFirst操作MySql时,提示 Specified key was too long; max key length is 767 bytes错误,但数据库和表也建成功了.有高人知 ...

  8. c# 数据库缓存依赖

    1.为缓存依赖项启动通知数据库 在vs开发人员命令提示中运行(切换到aspnet_regsql.exe所在目录,示例目录:C:\Windows\Microsoft.NET\Framework64\v4 ...

  9. 由WSDL文件生成WEB service server端C#程序(转)

    一般一个已经实现功能的WEB Server会发布自己的WSDL文件,供客户端生成代理类. 但有时是先有的server与client交互的接口定义(WSDL)文件,然后由server和client端分别 ...

  10. POJ 2446 Chessboard

    要求用占两格的长方形铺满平面上除去指定点 二分图匹配 #include <iostream> #include <cstdio> #include <cstring> ...