C# 事件和委托的用途及区别
▲简单委托的构成:
可以选择将委托类型看做只定义了一个方法的接口,将委托的实例看做实现了那个接口的一个对象。
1. 声明委托类型——定义委托
混乱的根源:容易产生歧义的“委托”
委托经常被人误解,这是由于大家喜欢用委托这个词来描述委托类型和委托实例。
Console.WriteLine("StringProcessor父类:" + typeof(StringProcessor).BaseType);//StringProcessor父类:System.MulticastDelegate
委托的基类:System.MulticastDelegate——的基类:System.Delegate
2. 为委托实例的操作找到一个恰当的方法
3、创建委托实例
需要一个方法以及(对于实例方法来说)调用方法的目标;
单纯创建一个委托实例却不在某一时刻调用它是没有什么意义的。看看最后一步——调用。
4. 调用委托实例
“委托实例被调用”中的“调用”对应的是invoke。
invoke理解为“唤出”某个东西来帮你调用一个信息不明的方法时,用invoke比call恰当。理解为唤出和调用区别不明显。
调用委托实例的两种方式——显式调用Invoke和使用C#的简化形式。一般情况下只需使用简化形式。
- 声明的委托实例.invoke(参数)
- 声明的委托实例(参数)
委托的实质是间接完成某种操作,这增大了复杂性(看看为了输出这点儿内容,用了多少行代码),但同时也增加了灵活性。
5.合并和删除委托
System.Delegate类型的静态方法Combine和Remove负责创建新的委托实例。
其中, Combine等价于+=,而Remove等价于-=
委托是不易变的:
委托实例类似于string,创建了委托实例后,有关他的一切就不能改变。
事件和委托的用途
委托delegate:
用途最广的是,使用委托为形参,传递实参(回调函数)时,可以使用匿名函数。对比Java:java使用匿名函数是形参是接口,创建一个接口实现类匿名类,匿名类实现接口方法。
例如:
var t4 = Task.Run(() => TaskMethod.DoTask("using Run method")); //系统方法Run 就使用了委托作为参数 public static Task<TResult> Run<[NullableAttribute(2)] TResult>(Func<TResult> function);
事件event:
开发者经常将事件和委托实例,或者将事件和委托类型的字段混为一谈。之所以产生混淆,原因和以前相同,因为C#提供了一种简写方式,允许使用字段风格的事件( field-like event)
字段风格的事件使所有这些的实现变得更易阅读,只需一个声明就可以了。 编译器会将声明转换成一个具有默认add/remove实现的事件和一个私有委托类型的字段。
事件不是委托实例——只是成对的add/remove方法(类似于属性的取值方法/赋值方法)事件包含一个私有委托类型字段。
void OnEventRaised(object sender, EventArgs args);
发布器类:
- 声明委托,事件委托一般命名为:NameEventHandler
- 声明事件
//event关键字代表事件,返回类型为委托; public event BoilerLogHandler BoilerEventLog;//基于委托定义事件,委托的函数指针。
- 创建引发事件的方法,一般命名为:OnEventName。
订阅器类
- 实例化发布器类
- 绑定事件:+= 委托(或函数都可以)
发布器类一个方法OnEventName() 等价于,订阅事件中的所有绑定方法一起执行。(广播)
public class Host { //定义委托原型 public delegate void OpenDoorEventHandler(); //定义委托类型的事件 public event GoHomeEventHandler OpenDoor; //定义内部一个方法,在这个方法内判断,OpenDoor事件是否被其他对象注册,一旦注册了,则调用执行事件。 protected void OnOpenDoor() { if(OpenDoor!=null) { OpenDoor(); } } public void GoHome() { OnOpenDoor(); } } public class Cat { public void Run() { //猫跑了 } } public class Dog { public void Bark() { //狗叫了 } } 如何使用如下: Host 主人 =new Host(); Cat 猫 = new Cat(); Dog 狗 = new Dog(); 主人.OpenDoor += new 主人.OpenDoorEventHandler(猫.Run); 主人.OpenDoor += new 主人.OpenDoorEventHandler(狗.Back); 主人.GoHome【发布器类】就等价于 猫.Run();//【订阅器类】 狗.Back();//【订阅器类】
当被观察者【发布器类】,做出某一特定“动作”(被观察者【发布器类】的特定“动作”【发布器类中引发事件的方法,一般命名为:OnEventName】,注册了N个不同对象的不同反映【订阅器类】),观察者【订阅器类】对这个特定“动作”做出不懂的反映。
2.2 事件用法三步曲
事件机制的使用方法可以归纳为3个步骤:
(1)事件发布者定义event以及事件相关信息 (2)事件侦听者订阅event (3)事件发布者触发event,自动调用订阅者的事件处理方法。
到这里可能有的小伙伴觉得 和 多播委托 一样,添加也是+=,取消也是-=
其实事件就是多播委托的一种封装,如果不使用事件,直接使用委托,能不能实现事件机制呢?答案是完全可以。
那么,为什么还要用事件呢?事件是怎么封装了委托呢?
实际上,在以上案例中,使用 event 关键字在一行上定义事件是C# 提供的事件的简化记法。在我们定义了上述事件后,编译器会自动生成如下代码:
private EventHandler<CarInfoEventArgs> newCarEvent; public event EventHandler<CarInfoEventArgs> NewCarEvent { add { newCarEvent += value; } remove { newCarEvent -= value; } }
这非常像字段及其属性的关系。注意到,委托字段 newCarEvent 是私有的,因此在外部不能直接为事件赋值,但可以通过公开的 += 和 -= 运算符为事件添加实例方法。 另外,事件 event 是一种数据类型,是一个已经声明的委托类,只能在某个类的内部声明,并且只能被该类调用,不能在命名空间中声明和使用。这一点与委托 delegate 的声明不同。
总结:事件与委托的区别在于两点:
(1)委托是一个类,可以在命名空间中声明;而事件只能在事件发布者内部定义,且只能被该类调用。
(2)可以直接使用一个方法为委托赋值,而事件只开放了 += 和 -= 运算符为其添加或删除方法。
————————————————
版权声明:本文为CSDN博主「wnvalentin」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wnvalentin/article/details/82254656