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();
          
        }
    }
}
相关推荐
一只鹿鹿鹿2 分钟前
信息化项目管理规范(参考Word文件)
java·大数据·运维·开发语言·数据库
XGeFei6 分钟前
python中子线程与主线程的关系
开发语言·python
Chase_______10 分钟前
【Java杂项】final 关键字详解:变量、方法、类限制与引用可变性
java·开发语言·python
ruxingli19 分钟前
Golang iota详解
开发语言·后端·golang
我材不敲代码20 分钟前
Python venv 虚拟环境从入门到精通 + uv 高性能替代工具实战指南
开发语言·python·uv
l1t32 分钟前
DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程18-20
开发语言·python
磊 子38 分钟前
STL之deque和list以及两者与vector的对比
开发语言·c++·list
凤山老林40 分钟前
DDD(领域驱动设计)在复杂业务系统中的落地指南
java·开发语言·数据库·ddd·领域驱动
凯瑟琳.奥古斯特1 小时前
子查询原理与实战案例解析
开发语言·数据库·职场和发展·数据库开发
Eiceblue1 小时前
Python 操作 Excel:数据分组、分类汇总与取消分组全解
开发语言·python·excel