C#的Event事件示例小白级剖析

1、委托Delegate

首先说一下delegate委托,委托是将方法作为参数进行传递。

cs 复制代码
// 定义了一个委托类型

public delegate void MyDelegate(int num);

// 定义了一个啥也不干的委托实例

public MyDelegate m_delegate = _ => {};

// 定义了一个和委托相同格式的方法

public void MyFun(int num){

        Console.WriteLine(num);

};  

// 添加新的委托

m_delegate += MyFun;

// 执行任务

m_delegate(2);

2、基于委托的发布/订阅模式

基于上例,对于如下的操作:

m_delegate += MyFun1;

m_delegate += MyFun2;

....

m_delegate(1);

这是一个委托的多播操作,一连串的任务会一起执行。

此时换一个思维,假如把+=操作看成是订阅操作。m_delegate(1)是发布,1可以看做是一个message,可以是int,也可以是string。这样就构成了一个发布/订阅模式。

当m_delegate(1)发布执行时,委托的多播会让所有订阅函数执行。

所以订阅者只需要把委托格式的函数告诉发布者(+=注册进去),当发布者发布时,所有的订阅者就相当于收到了消息。

下面写一个基于委托的发布/订阅模式:

cs 复制代码
#define DEBUG3

using System;
using System.Diagnostics;

namespace ConsoleApp1
{
    public delegate void SayGate(int num);

    public class Publisher
    {
        // 定义委托
        public SayGate SayGateHandler;

        // 触发任务
        public void OnTrigger(int num)
        {
            // 触发任务
            SayGateHandler(num);
        }
    }

    public class Subscriber
    {
        public void Subscribe(Publisher publisher)
        {
            // 订阅
            publisher.SayGateHandler += SayHello;
        }

        public void SayHello(int num)
        {
            Console.WriteLine("subscribe1: " + num);
        }
    }

    public class Subscriber2
    {
        public void Subscribe(Publisher publisher)
        {
            // 订阅
            publisher.SayGateHandler += SayHello;
        }

        public void SayHello(int num)
        {
            Console.WriteLine("subscribe2: " + num);
        }
    }

    internal class Program
    {
        
        static void Main(string[] args) {
            Publisher publisher = new Publisher();

            Subscriber subscriber = new Subscriber();
            subscriber.Subscribe(publisher);
            Subscriber2 subscriber2 = new Subscriber2();
            subscriber2.Subscribe(publisher);

            Console.ReadLine();
            publisher.OnTrigger(1);
            Console.ReadLine();
          
        }
    }
}

执行后会显示:

subscribe1: 1

subscribe2: 1

3、存在的缺陷

假如有个订阅者干了坏事,把"+="写成了"=",比如:

此时执行后会显示:

subscribe2: 1

即只显示了一条。

4、事件Event

针对上面的缺陷,需要引入一种机制限制订阅者的行为。

在C#中,event是一种特殊的委托,特殊之处为:订阅者只能通过"+="和"-="订阅或取消。发布者仍然可以进行"="操作。

当将SayGateHandler定义为event之后,订阅者2"="方式的订阅提示失败。

错误信息为:事件"Publisher.SayGateEvent"只能出现在+=或-=的左边(从类型"Publisher"中使用时除外)

新程序如下:

cs 复制代码
namespace ConsoleApp1
{
    public delegate void SayGate(int num);

    public class Publisher
    {
        // 定义委托
        public event SayGate SayGateHandler;

        // 触发任务
        public void OnTrigger(int num)
        {
            // 触发任务
            SayGateHandler(num);
        }
    }

    public class Subscriber
    {
        public void Subscribe(Publisher publisher)
        {
            // 订阅
            publisher.SayGateHandler += SayHello;
        }

        public void SayHello(int num)
        {
            Console.WriteLine("subscribe1: " + num);
        }
    }

    public class Subscriber2
    {
        public void Subscribe(Publisher publisher)
        {
            // 订阅
            publisher.SayGateHandler += SayHello;
        }

        public void SayHello(int num)
        {
            Console.WriteLine("subscribe2: " + num);
        }
    }

    internal class Program
    {
        
        static void Main(string[] args) {
            Publisher publisher = new Publisher();

            Subscriber subscriber = new Subscriber();
            subscriber.Subscribe(publisher);
            Subscriber2 subscriber2 = new Subscriber2();
            subscriber2.Subscribe(publisher);

            Console.ReadLine();
            publisher.OnTrigger(1);
            Console.ReadLine();
          
        }
    }
}

5、存在的缺陷2

以上程序有2个订阅者,但是假如没有订阅者,此时发布后会怎么样?

将以上代码注释后做测试,报错信息如下:

此处报错的意思是:SayGameHandler没有实例化。

有两种解决方案:

1)发布者实例化一个空的委托,

public event SayGate SayGateHandler = _ => { };

2)触发事件时检测下是否实例化,

// 触发任务

SayGateHandler?.Invoke(num);

两种方法选择一种即可。

最后一个完整的Event示例如下:

cs 复制代码
namespace ConsoleApp1
{
    public delegate void SayGate(int num);

    public class Publisher
    {
        // 定义委托
        public event SayGate SayGateHandler;

        // 触发任务
        public void OnTrigger(int num)
        {
            // 触发任务
            SayGateHandler?.Invoke(num);
        }
    }

    public class Subscriber
    {
        public void Subscribe(Publisher publisher)
        {
            // 订阅
            publisher.SayGateHandler += SayHello;
        }

        public void SayHello(int num)
        {
            Console.WriteLine("subscribe1: " + num);
        }
    }

    public class Subscriber2
    {
        public void Subscribe(Publisher publisher)
        {
            // 订阅
            publisher.SayGateHandler += SayHello;
        }

        public void SayHello(int num)
        {
            Console.WriteLine("subscribe2: " + num);
        }
    }

    internal class Program
    {
        
        static void Main(string[] args) {
            Publisher publisher = new Publisher();

            Subscriber subscriber = new Subscriber();
            subscriber.Subscribe(publisher);
            Subscriber2 subscriber2 = new Subscriber2();
            subscriber2.Subscribe(publisher);

            Console.ReadLine();
            publisher.OnTrigger(1);
            Console.ReadLine();
          
        }
    }
}
相关推荐
Ajiang28247353042 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
幽兰的天空2 小时前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10225 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
----云烟----7 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024067 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it7 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康7 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
九鼎科技-Leo8 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
转世成为计算机大神8 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式