event 关键字的来由,为了简化自定义方法的构建来为委托调用列表增加和删除方法。

在编译器处理 event 关键字的时候,它会自动提供注册和注销方法以及任何必要的委托类型成员变量。

这些委托成员变量总是声明为私有的,因此不能直接从触发事件对象访问它们。

温馨提示:如果您对于委托不是很了解,您可以先看 C#委托(Delegate) ,这对您理解本章会有所帮助。

定义一个事件的步骤:

  1. 需要定义一个委托,它包含事件触发时将要调用方法
  2. 通过 event 关键字用相关委托声明这个事件

话不多说,我们来看一个示例:

1. 定义Car类:

  1. public class Car
  2. {
  3. // 这个委托用来与Car事件协作
  4. public delegate void CarEngineHandler(string msg);
  5.  
  6.   // 这种汽车可以发送这些事件
  7. public event CarEngineHandler Exploded;
  8. public event CarEngineHandler AboutToBlow;
  9.  
  10. public int CurrentSpeed { get; set; }
  11.  
  12. public int MaxSpeed { get; set; }
  13.  
  14. public string PetName { get; set; }
  15.  
  16. private bool CarIsDead;
  17.  
  18. public Car()
  19. {
  20. MaxSpeed = ;
  21. }
  22.  
  23. public Car(string name, int maxSp, int currSp)
  24. {
  25. CurrentSpeed = currSp;
  26. MaxSpeed = maxSp;
  27. PetName = name;
  28. }
  29.  
  30. public void Accelerate(int delta)
  31. {
  32. // 如果Car无法使用了,触发Exploded事件
  33. if (CarIsDead)
  34. {
  35. if (Exploded != null)
  36. {
  37. Exploded("sorry,this car is dead");
  38. }
  39. }
  40. else
  41. {
  42. CurrentSpeed += delta;
  43.  
  44. // 确认已无法使用,触发AboutToBlow事件
  45. if ((MaxSpeed - CurrentSpeed) == && AboutToBlow != null)
  46. {
  47. AboutToBlow("careful buddy ! gonna blow !");
  48. }
  49.  
  50. if (CurrentSpeed >= MaxSpeed)
  51. {
  52. CarIsDead = true;
  53. }
  54. else
  55. {
  56. Console.WriteLine($"CurrentSpeed={CurrentSpeed}");
  57. }
  58. }
  59. }
  60. }

以上我们已经设定了Car对象发送两个自定义事件,这不再需要自定义注册函数,也不需要声明委托成员变量。稍后我们将说到如何使用这个汽车,在此之前,让我们了解一下事件的架构,揭开事件的神秘面纱。

2. 事件神秘面纱

C#事件事实上会扩展两个隐藏的公共方法,一个 add_事件名称,一个 remove_事件名称。

add_Exploded() CIL指令

remove_Exploded() CIL指令

代表事件本身的CIL代码使用 .addon 和 .removeon 指令调用对应的 add_xxx() 和 remove_xxx()方法

3. 使用Car类

了解了这些之后,我们来使用之前定义的Car类:

  1. public class MyEvent
  2. {
  3. public static void Show()
  4. {
  5. WriteLine("fun with events");
  6. Car c1 = new Car("bwm", , );
  7.  
  8. // 注册事件处理程序
  9. c1.AboutToBlow += new Car.CarEngineHandler(CarIsAlomostDoomed);
  10. c1.AboutToBlow += new Car.CarEngineHandler(CarAboutToBlow);
  11.  
  12. Car.CarEngineHandler d = new Car.CarEngineHandler(CarExploded);
  13. c1.Exploded += d;
  14.  
  15. WriteLine("******Speeding up******");
  16. for (int i = ; i < ; i++)
  17. {
  18. c1.Accelerate();
  19. }
  20.  
  21. // 注销,从调用列表中移除CarExploded()方法
  22. c1.Exploded -= d;
  23.  
  24. WriteLine("******Speeding up******");
  25. for (int i = ; i < ; i++)
  26. {
  27. c1.Accelerate();
  28. }
  29. }
  30. private static void CarExploded(string msg) => WriteLine($"CarExploded-> {msg}");
  31.  
  32. private static void CarAboutToBlow(string msg) => WriteLine($"CarAboutToBlow=>{msg}");
  33.  
  34. private static void CarIsAlomostDoomed(string msg) => WriteLine($"CarIsAlomostDoomed-> {msg}");
  35. }

运行效果图:

为了进一步简化事件注册,我们可以用到委托章节学习到的方法组转换语法(解释:我可以在调用以委托作为参数的方法时,直接提供方法的名称,而不是委托对象)

下面请看使用方法组转换,注册和注销事件,粗体部分:

  1. public static void Show()
  2. {
  3. WriteLine("fun with events");
  4. Car c1 = new Car("bwm", , );
  5.  
  6. // 注册事件处理程序
  7. c1.AboutToBlow += CarIsAlomostDoomed;
  8. c1.AboutToBlow += CarAboutToBlow;
  9. c1.Exploded += CarExploded;

  10. WriteLine("******Speeding up******");
  11. for (int i = ; i < ; i++)
  12. {
  13. c1.Accelerate();
  14. }
  15. // 注销,从调用列表中移除CarExploded()方法
  16. c1.Exploded -= CarExploded;

  17. WriteLine("******Speeding up******");
  18. for (int i = ; i < ; i++)
  19. {
  20. c1.Accelerate();
  21. }
  22. }

4. 创建自定义事件参数

微软的事件模式:(System.Object sender,System.EventArgs args)这一两个参数的模型。

第一个参数 sender :表示一个对发送事件的对象(Car)的引用,

第二个参数 args :与该事件相关的信息

System.EventArgs 基类源代码:

  1. public class EventArgs {
  2. public static readonly EventArgs Empty = new EventArgs();
  3.  
  4. public EventArgs()
  5. {
  6. }
  7. }

那么对于简单的事件类型来说,我们可以直接传递一个EventArgs的实例,但是如果我们期望传递自定义的数据,就应该从System.EventArgs派生出一个子类。
我们接下来就为我们的 Car 自定义一个符合这种事件模式的事件参数,新建一个 CarEventArgs 类,包含一个字符串,表示要发送给接收者的信息:

  1. public class CarEventArgs : EventArgs
  2. {
  3. public readonly string msg;
  4. public CarEventArgs(string message)
  5. {
  6. msg = message;
  7. }
  8. }

我们修改一下Car类,新添加一个 CarCustomEngineHandler 委托,并且更改相应的事件代码:

  1. public class Car
  2. {
  3. public delegate void CarCustomEngineHandler(object sender, CarEventArgs e);
  4.  
  5. // 模仿微软正规(object sender, EventArgs e)写法
  6. public event CarCustomEngineHandler CustomExploded;
  7. public event CarCustomEngineHandler CustomAboutToBlow;
  8.  
  9. public void AccelerateCustom(int delta)
  10. {
  11. if (CarIsDead)
  12. {
  13. if (CustomExploded != null)
  14. {
  15. CustomExploded(this, new CarEventArgs("sorry,this car is dead"));
  16. }
  17. }
  18. else
  19. {
  20. CurrentSpeed += delta;
  21.  
  22. if ((MaxSpeed - CurrentSpeed) == && CustomAboutToBlow != null)
  23. {
  24. CustomAboutToBlow(this, new CarEventArgs("careful buddy ! gonna blow !"));
  25. }
  26.  
  27. if (CurrentSpeed >= MaxSpeed)
  28. {
  29. CarIsDead = true;
  30. }
  31. else
  32. {
  33. Console.WriteLine($"CurrentSpeed={CurrentSpeed}");
  34. }
  35. }
  36. }
  37. }

看一下调用粗体部分(是如何使用传递的参数sender,e的):

  1. public class MyCustomEvents
  2. {
  3. public static void Show()
  4. {
  5. WriteLine("fun with events");
  6. Car c1 = new Car("bwm", , );
  7.  
  8. c1.CustomAboutToBlow += CarIsAlomostDoomed;
  9. c1.CustomAboutToBlow += CarAboutToBlow;
  10.  
  11. Car.CarCustomEngineHandler d = CarExploded;
  12. c1.CustomExploded += d;
  13.  
  14. WriteLine("******Speeding up******");
  15. for (int i = ; i < ; i++)
  16. {
  17. c1.AccelerateCustom();
  18. }
  19.  
  20. c1.CustomExploded -= d;
  21.  
  22. WriteLine("******Speeding up******");
  23. for (int i = ; i < ; i++)
  24. {
  25. c1.AccelerateCustom();
  26. }
  27. }
  28.  
  29. private static void CarExploded(object sender, CarEventArgs e) => WriteLine($"CarExploded->{((Car)sender)?.PetName} {e.msg}");
  30. private static void CarAboutToBlow(object sender, CarEventArgs e) => WriteLine($"CarAboutToBlow=>{((Car)sender)?.PetName} {e.msg}");
  31. private static void CarIsAlomostDoomed(object sender, CarEventArgs e) => WriteLine($"CarIsAlomostDoomed->{((Car)sender)?.PetName} {e.msg}");
  32. }

5. 泛型 EventHandler<T> 委托

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

由于很多自定义委托接受(object,EventArgs)这样的参数结构,那么我们可以使用框架内置的 EventHandler<> 来简化我们的事件 委托。

首先修改一下Car类:

  1. public class Car
  2. {
  3. public event EventHandler<CarEventArgs> StandardExploded;
  4. public event EventHandler<CarEventArgs> StandardAboutToBlow;
  5.  
  6. public void AccelerateStandard(int delta)
  7. {
  8. if (CarIsDead)
  9. {
  10. if (StandardExploded != null)
  11. {
  12. StandardExploded(this, new CarEventArgs("sorry,this car is dead"));
  13. }
  14. }
  15. else
  16. {
  17. CurrentSpeed += delta;
  18.  
  19. if ((MaxSpeed - CurrentSpeed) == && StandardAboutToBlow != null)
  20. {
  21. StandardAboutToBlow(this, new CarEventArgs("careful buddy ! gonna blow !"));
  22. }
  23.  
  24. if (CurrentSpeed >= MaxSpeed)
  25. {
  26. CarIsDead = true;
  27. }
  28. else
  29. {
  30. Console.WriteLine($"CurrentSpeed={CurrentSpeed}");
  31. }
  32. }
  33. }
  34. }

调用代码其实和上一段并没有太大差异,这里还是贴出来:

  1. public class MyStandardEvent
  2. {
  3. public static void Show()
  4. {
  5. WriteLine("fun with events");
  6. Car c1 = new Car("bwm", , );
  7.  
  8. c1.StandardAboutToBlow += CarIsAlomostDoomed;
  9. c1.StandardAboutToBlow += CarAboutToBlow;
  10.  
  11. EventHandler<CarEventArgs> d = CarExploded;
  12. c1.StandardExploded += d;
  13.  
  14. WriteLine("******Speeding up******");
  15. for (int i = ; i < ; i++)
  16. {
  17. c1.AccelerateStandard();
  18. }
  19.  
  20. c1.StandardExploded -= d;
  21.  
  22. WriteLine("******Speeding up******");
  23. for (int i = ; i < ; i++)
  24. {
  25. c1.AccelerateStandard();
  26. }
  27. }
  28.  
  29. private static void CarExploded(object sender, CarEventArgs e) => WriteLine($"CarExploded->{((Car)sender)?.PetName} {e.msg}");
  30.  
  31. private static void CarAboutToBlow(object sender, CarEventArgs e) => WriteLine($"CarAboutToBlow=>{((Car)sender)?.PetName} {e.msg}");
  32.  
  33. private static void CarIsAlomostDoomed(object sender, CarEventArgs e) => WriteLine($"CarIsAlomostDoomed->{((Car)sender)?.PetName} {e.msg}");
  34. }

6.匿名方法

这么简单的处理操作, CarExploded() ,CarAboutToBlow()这一的方法很少会被调用委托之外的任何程序所调用。从生成效率来说,手工定义一个由委托对象调用的方法有点麻烦耶。

为了解决这种情况,现在事件注册时,可以直接将一个委托与一段代码关联 -- 匿名方法

我们修改一下调用Car类的地方(注意粗体部分、最后一个大括号 ";" 结束):

  1. public class MyAnonymousMtehoden
  2. {
  3. public static void Show()
  4. {
  5. int aboutToBlowCounter = ;
  6.  
  7. WriteLine("fun with events");
  8. Car c1 = new Car("bwm", , );
  9.  
  10. c1.StandardAboutToBlow += delegate
  11. {
  12. WriteLine("Eek,going to fast");
  13. };
  14.  
  15. c1.StandardAboutToBlow += delegate (object sender, CarEventArgs e)
  16. {
  17. aboutToBlowCounter++;
  18. WriteLine($"CarAboutToBlow=>{((Car)sender)?.PetName} {e.msg}");
  19. };
  20.  
  21. c1.StandardExploded += delegate (object sender, CarEventArgs e)
  22. {
  23. aboutToBlowCounter++;
  24.  
  25. WriteLine($"Exploded=>{((Car)sender)?.PetName} {e.msg}");
  26. };
  27. for (int i = ; i < ; i++)
  28. {
  29. c1.AccelerateStandard();
  30. }
  31.  
  32. WriteLine($"aboutToBlowCounter={aboutToBlowCounter}");
  33. }
  34. }

本文参考《精通C#》

学无止境,望各位看官多多指教。

C#事件(Event)学习日记的更多相关文章

  1. 学习笔记---Javascript事件Event、IE浏览器下的拖拽效果

    学习笔记---Javascript事件Event.IE浏览器下的拖拽效果     1. 关于event常用属性有returnValue(是否允许事件处理继续进行, false为停止继续操作).srcE ...

  2. Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群

    Redis总结(五)缓存雪崩和缓存穿透等问题   前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...

  3. Lite OS学习之事件EVENT

    1. Lite OS的事件EVENT,就是一个任务向另外一个任务通知事件的,不能数据传输.看下有的函数,实际比较复杂 2. 具体还是看编程,先全局结构体整个事件变量 /*事件控制结构体*/ EVENT ...

  4. libevent源码学习(9):事件event

    目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...

  5. android学习日记03--常用控件button/imagebutton

    常用控件 控件是对数据和方法的封装.控件可以有自己的属性和方法.属性是控件数据的简单访问者.方法则是控件的一些简单而可见的功能.所有控件都是继承View类 介绍android原生提供几种常用的控件bu ...

  6. MySQL 定时器EVENT学习

    原文:http://blog.csdn.net/lifuxiangcaohui/article/details/6583535 MySQL 定时器EVENT学习 MySQL从5.1开始支持event功 ...

  7. 经典线程同步 事件Event

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇 一个经典的多线程同步问题> <秒杀多线程第五篇 经典线程同步关键段CS> 上一篇中使用关键段来解决经典的多线程同步互斥问题 ...

  8. android学习日记0--开发需要掌握的技能

    一.开发android,我们需要哪些技能基础 1.Java基础知识 2.Linux基础知识 3.数据库基础知识 4.网络协议 5.Android基础知识 6.服务器端开发知识 1.Java基础知识 很 ...

  9. android学习日记03--常用控件Dialog

    常用控件 9.Dialog 我们经常会需要在Android界面上弹出一些对话框,比如询问用户或者让用户选择.这些功能我们叫它Android Dialog对话框 对话框,要创建对话框之前首先要创建Bui ...

  10. 多线程面试题系列(6):经典线程同步 事件Event

    上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的"线程所有权"特性所以关键段只能用于线程的互斥而不能用于同步.本篇介绍用事件Event来尝试解决这个线程同步问题.首先 ...

随机推荐

  1. Character frequency

    地址:http://www.codewars.com/kata/53e895e28f9e66a56900011a/train/python Write a function that takes a ...

  2. BTrace: DTrace for Java2

    BTrace: DTrace for Java… ish 时间 2012-04-24 16:17:55  dtrace.org 原文  http://dtrace.org/blogs/ahl/2012 ...

  3. JDK8新特性之接口

    在JDK7及以前的版本中,接口中都是抽象方法,不能定义方法体,但是从jdk8开始,接口中可以定义静态的非抽象的方法,直接使用接口名调用静态方法,但是它的实现类的类名或者实例却不可以调用接口中的静态方法 ...

  4. Ubuntu 13.10 Rhythmbox 播放器不能播放MP3。安装插件

    Ctrl+Alt+T > sudo apt-get install ubuntu-restricted-extras 因为版权和专利的问题,MP3等一些non-free的格式文件支持没有出现在免 ...

  5. android获取Mac地址和IP地址

    获取Mac地址实际项目中测试了如下几种方法:(1)设备开通Wifi连接,获取到网卡的MAC地址(但是不开通wifi,这种方法获取不到Mac地址,这种方法也是网络上使用的最多的方法) //根据Wifi信 ...

  6. node express

    在某QQ群里,发现大家都在搞node,为了不被out,这周主要研究了一下,还挺高大上. 参考了下资料,适合初学者学习. Node和NPM的安装够便捷了,不细说...有几点基础顺手提一下: 安装命令中的 ...

  7. [Mime] MimeHeaders--MimeHeader帮助类 (转载)

    点击下载 MimeHeaders.rar 这个类是关于Mime的Headers类看下面代码吧 /// <summary> /// 类说明:Assistant /// 编 码 人:苏飞 // ...

  8. 浅析Activity不可见与透明

    http://blog.csdn.net/lincyang/article/details/6868582 看见标题也许你会有疑问,不可见和透明不是一个意思吗? 从字面上看,这还真是差不多.但在Act ...

  9. Android- Context理解

    学习了安卓以后还是不理解Context的用法:Api文档链接http://wear.techbrood.com/reference/android/content/Context.html 一个非常好 ...

  10. Tomcat-java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory

    好些天没弄java了,今天开MyEclipse,发现启动Tomcat的时候发错了,后来发现,报错如题. 解决方案是将 bin/tomcat-juli.jar 添加到add tomcat classpa ...