C#-事件event
1. 简介
事件是一种类型安全的委托(具体实例说明见《精通C#》 --10.5 C#事件)
为什么这么说呢?可以类比属性和字段,属性是对字段的封装,其实 事件也就是封装了一个委托。但是你要知道:委托是一种自定义的数据类型,事件只是一种类的成员。
那么现在你要问:事件到底有什么具体的作用?
事件呢?就是有一段代码(方法)触发(raise)事件,事件被触发后则订阅事件的事件处理程序(event handler) 就会开始执行。
从这点看,事件类似异常,因为他们都是由对象引发的,并且可以由我们提供的代码处理
订阅一个事件的含义:是提供代码在事件发生时执行这些代码,这些代码称为事件处理程序
订阅事件的语法如下:
事件名+=new 委托名(方法名);
事件名+=函数名注意订阅事件的可以是委托对象,也可以直接是一个函数或是匿名函数或Lambda表达式
只要这个函数的返回值类型和签名与事件一致即可
我们都知道委托是类似c语言中的函数针,是一种函数指针在面向对象中的封装,是一种函数回调机制(回调函数就是一通过函数指针调用函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的数时,我们就说这是回调函数。);
事件是用户与应用程序交互的基础,它是回调机制的一种应用。
简而言之:委托的本质是引用类型,用包装回调函数,用于实现回调机制。事件的本质是一个类型安全的(多播)委托,事件是回调机制的一种应用。
事件和委托的关系
事件的委托类似属性和字段,事件是对委托的封装,这句话意思就是:事件是一类型安全的委托
事件比委托有更多的限制
1、事件只能同“+=”和“-=”来绑定方(在事件中这个方法叫事件的处理程序,其实这这种绑定方法就是多播委托的绑定方法
2、只能在类的内部调用(触发)事件,但是委托就可以在类外调用函数那样调用
2.实际案例
2.1实际案例1
代码实际背景:
当裁判的发令枪响起,触发事件,事件触发执行的动作就是运动员跑起来了
裁判是发布者,触发事件的方法是发令枪响,
运动员是事件的订阅者,事件发生后就开跑。
class Program
{
static void Main(string[] args)
{
//实例化事件发布者
Judgment judgment = new Judgment();
//实例化事件订阅者
RunSporters runsporter = new RunSporters();
// 订阅者类RunSporters中的方法Run()和RunFail()订阅事件eventRun
//通过委托订阅
judgment.eventRun += new Judgment.delegateRun(runsporter.Run);
//通过函数订阅
judgment.eventRun += runsporter.RunFail;
//执行引发事件的方法,这句代码之后事件的订阅者中的事件处理程序Run()和RunFail()开始运行
judgment.Begin();
Console.ReadKey();
}
}
//事件发布者,也称发布器(publisher)
//发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。
//发布器(publisher)类的对象调用这个事件,并通知其他的对象。
class Judgment
{
//定义一个委托(你要明白:这个委托类型就是事件处理程序的函数类型)
public delegate void delegateRun();
//定义一个事件
//事件的声明与之前委托变量delegate1的声明唯一的区别是多了一个event关键字,且委托写“()”,事件不需要
public event delegateRun eventRun;
//引发事件的方法
//当方法Begin()被执行了,此时就会触发事件 eventRun
public void Begin()
{
if (eventRun != null)
{
//被引发的事件
eventRun();
//☆☆☆☆☆☆☆☆☆☆☆☆
//注意这里就是体现“事件只能在类的内部调用”的地方,你在这个发布者类之外不能调用事件eventRun
}
}
}
//事件订阅者,也称订阅器(subscriber)
//订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。
//在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
//一个事件可以有多个订阅者,事件的发布者也可以是事件的订阅者。
public class RunSporters
{
//事件处理程序
public void Run()
{
Console.WriteLine("运动员跑起来了");
}
//事件处理程序
public void RunFail()
{
Console.WriteLine("有一个起跑失败");
}
}
2.2实际案例2--带有参数的事件
代码实际背景:
热水器仅仅负责烧水,有一个温度字段temperature ,它不能发出警也不能显示水温;
在水烧开时由警报器发出警报;
显示器显示提示和水温。
观察者模式:
Subject:监视对象,它往往包含着其他对象所感兴趣的内容。
在本范例中,热水器就是一个监视对象,它包含的其他对象所感兴趣的内容,就是temprature字段,当这个字段的值快到100时,会不断把数据发给监视它的对象。
Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。
在本范例中,Observer有警报器和显示器,它们采取的行动分别是发出警报和显示水温。
在本例中,事情发生的顺序应该是这样的:
警报器和显示器告诉热水器,它对它的温度比较感兴趣(注册)。
热水器知道后保留对警报器和显示器的引用。
热水器进行烧水这一动作,当水温超过95度时,通过对警报器和显示器的引用,自动调用警报器的MakeAlert()方法、显示器的ShowMsg()方法。
//发布者--热水器
public class Heater
{
private int temperature;
public delegate void BoilHandler(int param); //声明委托
public event BoilHandler BoilEvent; //声明事件
// 烧水
public void BoilWater()
{
for (int i = 0; i <= 100; i++)
{
temperature = i;
//当温度大于95度开始触发事件
if (temperature > 95)
{
//如果有对象注册
if (BoilEvent != null)
{
BoilEvent(temperature); //调用所有注册对象的方法(事件处理程序)
}
}
}
}
}
//订阅者--显示器
public class Display
{
//静态方法
public static void ShowMsg(int param)
{
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。\n", param);
}
}
// 订阅器--- 警报器
public class Alarm
{
public void MakeAlert(int param)
{
Console.WriteLine("Alarm:嘀嘀嘀,水已经{0} 度了:", param);
}
}
class Program
{
static void Main(string[] args)
{
//注意我们不需要实例化Display类,为什么呢?因为我们只是使用它里面的一个静态方法,直接使用类名点
Heater heater = new Heater();
Alarm alarm = new Alarm();
//注册方法(订阅事件)
//法1
heater.BoilEvent += new Heater.BoilHandler(alarm.MakeAlert);
//法2
heater.BoilEvent += (new Alarm()).MakeAlert; //给匿名对象注册方法
heater.BoilEvent += Display.ShowMsg; //注册静态方法,静态类中的方法调用直接使用类名点
heater.BoilWater(); //触发事件,会自动调用注册过对象的方法
Console.ReadKey();
}
}
3.标准事件的用法
C#的BCL(基础类库)中针对事件定义的时候所需要的委托有一个标准委托
public delegate void EventHandler(Object sender, EventArgs e);
其中:
第一个参数sender 用来保存触发事件的对象的引用。由于是Object类型的,所以可以匹配任何类型的实例。
第二个参数用来保存有该事件相关的信息
3.1通过扩展EventArgs来传递数据
注意到Eventhandler的第二个参数是EventArgs类型的,
那么我们来看看EventArgs类
public class EventArgs
{
public static readonly EventArgs Empty;
public EventArgs();
}
EventArgs被设计为不能传递任何数据,如果你想要传递数据怎么办?
我们可以通过扩展EventArgs来传递数据:也就是声明一个EventArgs的子类,使用合适的字段来保存需要传递的数据
比如,,我们要传递一个int类型的参数:
public class ExtendEventArgs:EventArgs
{
public int Tem;
public ExtendEventArgs(int tem)
{
Tem=tem;
}
}
那么这时候我们就可以这样声明EventHandler委托
public delegate void EventHandler(Object sender,ExtendEventArgs e);
你要知道的是C#2.0中引入了EventHandler泛型委托:
所以你不需要因为修改参数而重新声明EventHandler委托
你直接这样定义事件:
public event EventHandler<ExtendEventArgs> MyEvent;
3.2代码实例
代码背景:
汽车销售类CarDealer和顾客类Consumer
CarDealer提供一个新车到达出发事件,Consumer类订阅该事件
class Program
{
static void Main(string[] args)
{
CarDealer dealer = new CarDealer();
Consumer consumer=new Consumer ("志铭");
dealer.NewCarEvent += consumer.ConsumerReply;
dealer.RaiseNewCarInfo("BMW");
Console.ReadKey();
}
//扩展EventArgs类,添加一个Car属性用于传递数据
public class CarInfoEventArgs : EventArgs
{
public CarInfoEventArgs(string car)
{
this.Car = car;
}
public string Car;
}
//发布类
public class CarDealer
{
//public delegate EventHandler(object sender, CarInfoEventArgs e);//声明委托EventHandler
//public event EventHandler NewCarEvent;//声明事件NewCarEvent
//下面一行就可以代替上面注释的两行
//声明事件NewCarIfno
public event EventHandler<CarInfoEventArgs> NewCarEvent;
//触发事件NewCarIfno的函数
public void RaiseNewCarInfo(string car)
{
Console.WriteLine($"CarDealer :new car {car}");
if (NewCarEvent != null)
{
NewCarEvent(this, new CarInfoEventArgs(car));
//注意:事件的参数类型,声明事件时所用的委托的参数类型和事件处理程序的参数类型三者一样
//注意这里的参数this
//你想想为什么写this?
//EventHandler委托的两个参数,第一个参数就是触发事件的事件对象
}
}
}
//订阅类
public class Consumer
{
public string Name;
public Consumer (string name)
{
this.Name = name;
}
//事件处理程序,注意参数和事件的委托EventHandler<CarInfoEventArgs>一样
public void ConsumerReply(object sender, CarInfoEventArgs e)
{
Console.WriteLine($"{this.Name }:car {e.Car } very good!");
}
}
}
运行结果
CarDealer:new car BMW
志铭:car BMW very good!
4.示例源代码下载
C#-事件event的更多相关文章
- 事件EVENT与waitforsingleobject的使用
事件event与waitforsingleobject的配合使用,能够解决很多同步问题,也可以在数据达到某个状态时启动另一个线程的执行,如报警. event的几个函数: 1.CreateEvent和O ...
- 经典线程同步 事件Event
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇 一个经典的多线程同步问题> <秒杀多线程第五篇 经典线程同步关键段CS> 上一篇中使用关键段来解决经典的多线程同步互斥问题 ...
- C#事件(event)解析
事件(event),这个词儿对于初学者来说,往往总是显得有些神秘,不易弄懂.而这些东西却往往又是编程中常用且非常重要的东西.大家都知道windows消息处理机制的重要,其实C#事件就是基于window ...
- 【温故知新】c#事件event
从上一篇文章[温故知新]C#委托delegate可知,委托delegate和事件Event非常的相似,区别就是event关键字,给delegate穿上了个“马甲”. 让我们来看官方定义: 类或对象可以 ...
- 事件[event]_C#
事件(event): 1. 事件是类在发生其关注的事情时用来提供通知的方式.例如,封装用户界面控件的类可以定义一个在单击该控件时发生的事件.控件类不关心单击按钮时发生了什么,但它需要告知派 ...
- C#中的委托(Delegate)和事件(Event)
原文地址:C#中的委托(Delegate)和事件(Event) 作者:jiyuan51 把C#中的委托(Delegate)和事件(Event)放到现在讲是有目的的:给下次写的设计模式--观察者(Obs ...
- MFC线程(三):线程同步事件(event)与互斥(mutex)
前面讲了临界区可以用来达到线程同步.而事件(event)与互斥(mutex)也同样可以做到. Win32 API中的线程事件 HANDLE hEvent = NULL; void MainTestFu ...
- 重温委托(delegate)和事件(event)
1.delegate是什么 某种意义上来讲,你可以把delegate理解成C语言中的函数指针,它允许你传递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m,说白了就是可以把方法当作 ...
- C#总结(二)事件Event 介绍总结
最近在总结一些基础的东西,主要是学起来很难懂,但是在日常又有可能会经常用到的东西.前面介绍了 C# 的 AutoResetEvent的使用介绍, 这次介绍事件(event). 事件(event),对于 ...
- 多线程面试题系列(6):经典线程同步 事件Event
上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的"线程所有权"特性所以关键段只能用于线程的互斥而不能用于同步.本篇介绍用事件Event来尝试解决这个线程同步问题.首先 ...
随机推荐
- Python - 命令式编程与符号编程
原文链接:https://zh.d2l.ai/chapter_computational-performance/hybridize.html本文是对原文内容的摘取和扩展. 命令式编程(imperat ...
- 【从零开始自制CPU之学习篇05】总线
总线定义:总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束, 按照计算机所传输的信息种类,计算机的总线可以划分为数据总线.地址总线和控制总线,分别用来传输数据.数 ...
- 最近要租房子,用Python看一下房源吧..
前言:最近我的朋友想要租房子,为了装个b,决定运用技术去帮助他. 这个网站是什么我也不知道 反正是一个房子交易网站 http://www.ljia.net/ 设置请求头 headers = {'Ac ...
- 玩转ASP.NET Core中的日志组件
简介 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Console ...
- redis 系列13 集合对象
一. 集合对象概述 这里的集合是string类型的无序集合,在集合对象中集合成员是唯一的,这就意味着集合中不能出现重复的数据.集合是通过哈希表实现的,集合中最大的成员数为 232-1 (4294967 ...
- sql server 性能调优之 SQL语句阻塞查询
在生产环境下,有时公司客服反映网页半天打不到,除了在浏览器按F12的Network响应来排查,确定web服务器无故障后.就需要检查数据库是否有出现阻塞 当时数据库的生产环境中主表数据量超过2000w, ...
- Android--从系统Gallery获取图片
前言 在Android应用中,经常有场景会需要使用到设备上存储的图片,而直接从路径中获取无疑是非常不便利的.所以一般推荐调用系统的Gallery应用,选择图片,然后使用它.本篇博客将讲解如何在Andr ...
- 大战Java虚拟机【0】——目录
发现一直在上层应用更新博客的我,突然发现也是时候同时更新一波基础知识了.然后就发现了一个点我还没有更新过,那就是Java虚拟机. 很多人在学习Java的时候都或多或少接触过一点,然后几乎有下面几种情况 ...
- MongoDB的安全写入GetLastError
这次聊聊业务中经常出现的重试现象,可能很多运维都被开发莫名其妙的艾特然后让查一查业务中出现失败的情况,很不巧刚接手MongoDB的运维就碰到了一个案例. 前段时间与业务开发讨论过某业务服务的超时重试问 ...
- SpringCloud系列——Eureka 服务注册与发现
前言 Eureka是一种基于REST(具像状态传输)的服务,主要用于AWS云中定位服务,以实现中间层服务器的负载平衡和故障转移.本文记录一个简单的服务注册与发现实例. GitHub地址:https:/ ...