一个广为流传的错误说法:事件是一种特殊的委托
希望看完这个例子你能理解事件的意义,事件与委托的关系,以及上面那句话为什么不合理
假设你有一个公司,你有10名员工,1000元的股票市值,公司倒闭会造成裁员9人,市值蒸发900元。你会怎么写?
基本实现 缺点耦合
使用委托解耦 但麻烦
增加需求:公司倒闭员工不能买股票
1.将需要的参数依次传入给公司倒闭事件的响应方法,有n种类型的参数就需要声明n种委托,有n个无论什么类型的参数就需要在委托中写入n个参数。参数的数量和委托的数量都会随着参数的增加使代码变得臃肿。
2.需要更改多处参数以修改功能:声明委托的参数,调用委托的参数,响应方法的参数都需要同步修改,非常麻烦且不易修改。
使用EventArgs传递参数
解决方案是声明一个专门用来存储事件所需传递参数的类,有两点需要注意:
1.通常将该类命名为事件名 + EventArgs
2.通常需要继承EventArgs类:在该例子中调用响应方法的委托类型是我们定义的OnCompanyFailDelegate委托,但当我们使用C#为我们准备的C#准备好的专用处理事件的全局委托EventHandler时,我们的自定义事件参数类CompanyFailEventArgs类只有继承于EventArgs类才可以作为参数传入该委托,但由于非泛型委托EventHandler内的EventArgs是空的,这个委托几乎从来不用,一般用的是EventHandler,T就是我们自定义的事件参数类也就不需要继承自EventArgs类了。因此更多的意义是作为标识符让其他人知道继承自该类的类是用来存储事件参数的。
PS:C#其实为我们准备好了很多委托类型,使用它们我们可以省略声明自定义委托的那一步:Action无参无返回值委托,Action有参无返回值委托(最多12个T)、Func有参有返回值(参数中最后一个)
4.再次添加一个需求 公司倒闭后公司停止招人:
无论事件参数需要多少个、多少种类型,只需声明一个委托,委托参数用EventArgs类的一个参数取代将来可能需要的无数委托与其中长长的参数串。
增加参数时也不需要修改3处参数只需要在事件参数类中添加对应参数,并在事件拥有者调用委托前对参数赋值即可。
其实事件参数类与我们在Unity中做人物控制脚本时声明的InputControl类的存在意义类似,将其他动画状态脚本中所需要的参数传过去而不再需要在每个脚本中声明一次。这其实就是一种“面向对象”的封装设计思维,将共同需要的封装起来以便复用,也方便管理。
目前仍有一个小问题:如果需要传递事件拥有者类的对象作为参数,但目前的事件参数是在EventArgs类内声明的,在一个类中声明另一个类的对象这显然违背“低耦合”的设计思维。此外这是没有必要的,事件的触发逻辑只能也只会在事件的拥有者类中,在调用的时候99%的时候直接传入this关键字即事件拥有者的对象也并不麻烦。
object sender:表示触发事件的控件对象
EventArgs e:表示事件数据的类的基类
5.再次添加一个需求:object o的作用:事件的响应者反过来可以影响事件的拥有者
如果市值每减少100,员工每裁员1人,公司的信誉值就会减1
到这里其实与事件的完整声明就差一个event关键字了,而event关键字也正是为了完善发布订阅者模式的最后一步,解决5版本中隐藏的问题:消除可以绕过事件触发逻辑直接调用事件处理方法的可能性(刘铁猛老师形容的借刀杀人)
现在的写法以下的操作是被允许的:
公司并没有倒闭,没有通过触发公司倒闭事件的逻辑就调用了公司倒闭事件所绑定的响应方法
在事件拥有者类外调用事件的委托
使用事件,对委托的限制,安全可靠
:事件的委托类型用不用EventHandler<>都行,用的原因只有一个就是省事,省去我们声明一个委托类型并输入“固定但不必须”的事件参数。以下三种写法在最终版中都能正常运行,无区别
件的委托参数不一定非得是object o 和Eventargs args,不怕费事完全可以用一开始的方法事件参数类都不需要声明就一个一个参数写进去。如果用EventHandler委托作为事件的委托才一定是,事件的专属委托EventHandler<>只是一个模板,最省事也最合理的模板,不必须但应该这样写,否则你将委托声明为时间的委托有何意义呢?
事件是C#为实现发布订阅者模式发明的语法糖,即给委托加了3条限制和1个建议,
事件 = 单播/多播委托 + 蒙版,事件的委托限制了委托本身的一些功能:
1.声明位置:只能声明在某类中
2.调用位置 :只能在声明事件的委托的类中用 委托对象()调用,类外只能访问而不能调用
3.事件的委托的实例在类外只能出现在 +=/-=的左边,不能为 = ,只有类内可以
1.建议。委托的为object o,Eventargs args ,为此还专设EventHandler委托作为事件委托的最简写法
触发事件本质是调用事件的委托即调用委托所绑定的方法。
事件发生需要有一个主体,例如:倒闭Event,谁倒闭了?哦原来是“公司”倒闭了,同时也为了防止“张冠李戴”,例如“按钮倒闭了”这种荒谬的事情,因此事件必须声明在某一个类中(不像委托可以声明在类中亦可以声明在类外)在该例子中事件必须声明在公司Class(发布者)内,只能由公司类来触发事件,按钮类无法触发该事件。
公司倒闭肯定会有很多的影响,这些影响就是事件的各种响应方法,例如员工类(订阅者)的:员工失业方法,股票类(订阅者)的:股票大跌方法 etc 。 为了掐断“借刀杀人”的可能性,即公司没倒闭就意外触发这些响应方法,C#又规定事件对应的委托只能由类内的逻辑(判断语句 / 方法)调用而不能在类外调用,比如公司类内的“ 交易大失败方法 ” / “ if资金小于5000语句 ” 中调用公司倒闭事件的 委托。
为了实现这一点。规定事件对应的委托不能在类外用赋值符号“=”赋值,只能由+= / -= 增减委托方法,因为一旦允许,在公司类外创建事件对应的委托对象就是被允许的操作,通过这个新的委托对象和公司类内的委托对象都引用着委托所绑定的事件响应方法,通过类外创建的新实例就可以绕过事件拥有者内的触发事件逻辑。同时限定事件的委托不能在类外被调用只能被访问,如OnCompanyFail()在类外是不被允许的。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.youkuaiyun.com/jshxjd/article/details/135573530