C# 事件和委托的用途及区别

 

▲简单委托的构成:

可以选择将委托类型看做只定义了一个方法的接口,将委托的实例看做实现了那个接口的一个对象。

 

 

1. 声明委托类型——定义委托

混乱的根源:容易产生歧义的“委托”
委托经常被人误解,这是由于大家喜欢用委托这个词来描述委托类型和委托实例。
Console.WriteLine("StringProcessor父类:" + typeof(StringProcessor).BaseType);//StringProcessor父类:System.MulticastDelegate

 

委托的基类:System.MulticastDelegate——的基类:System.Delegate

2. 为委托实例的操作找到一个恰当的方法

3、创建委托实例

需要一个方法以及(对于实例方法来说)调用方法的目标;

单纯创建一个委托实例却不在某一时刻调用它是没有什么意义的。看看最后一步——调用。

4. 调用委托实例

“委托实例被调用”中的“调用”对应的是invoke。
invoke理解为“唤出”某个东西来帮你调用一个信息不明的方法时,用invoke比call恰当。理解为唤出和调用区别不明显。
调用委托实例的两种方式——显式调用Invoke和使用C#的简化形式。一般情况下只需使用简化形式。

  1. 声明的委托实例.invoke(参数)
  2. 声明的委托实例(参数)

委托的实质是间接完成某种操作,这增大了复杂性(看看为了输出这点儿内容,用了多少行代码),但同时也增加了灵活性。

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

 

posted @ 2020-03-17 16:05  好Wu赖  阅读(2960)  评论(0编辑  收藏  举报