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();
          
        }
    }
}
相关推荐
安大小万16 分钟前
C++ 学习:深入理解 Linux 系统中的冯诺依曼架构
linux·开发语言·c++
随心Coding20 分钟前
【零基础入门Go语言】错误处理:如何更优雅地处理程序异常和错误
开发语言·后端·golang
T.Ree.24 分钟前
C语言_自定义类型(结构体,枚举,联合)
c语言·开发语言
Channing Lewis25 分钟前
python生成随机字符串
服务器·开发语言·python
pchmi1 小时前
C# OpenCV机器视觉:红外体温检测
人工智能·数码相机·opencv·计算机视觉·c#·机器视觉·opencvsharp
小熊科研路(同名GZH)1 小时前
【Matlab高端绘图SCI绘图模板】第002期 绘制面积图
开发语言·matlab
鱼是一只鱼啊1 小时前
.netframeworke4.6.2升级.net8问题处理
开发语言·.net·.net8
Tanecious.1 小时前
C语言--数据在内存中的存储
c语言·开发语言·算法
咸甜适中1 小时前
go语言gui窗口应用之fyne框架-动态添加、删除一行控件(逐行注释)
开发语言·后端·golang