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();
          
        }
    }
}
相关推荐
waicsdn_haha5 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
_WndProc7 分钟前
C++ 日志输出
开发语言·c++·算法
web147862107239 分钟前
C# .Net Web 路由相关配置
前端·c#·.net
qq_4335545416 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
数据小爬虫@35 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.37 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy43 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader1 小时前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默1 小时前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Jasmine_llq1 小时前
《 火星人 》
算法·青少年编程·c#