当前位置: 首页 > article >正文

图解C#高级教程(二):事件

在现实生活当中,有一些事情发生时,会连带另一些事情的发生。例如,当某国的总统发生换届时,不同党派会表现出不同的行为。两者构成了“因果”关系,因为发生了A,所以发生了B。在编程语言当中,具有类似的概念。在编程语言中,发生的事情 A 称为事件,因 A 发生的事情 B 称为对事件的处理或者响应(事件处理程序)。

本章主要讲解 C# 语言当中的发布者-订阅者模式的概念、代码实现该模式时的组成部分以及标准事件的用法。

1. 发布者和订阅者的概念

在程序中,我们很多时候会面临这样一个需求:当一个特定的程序事件发生时,程序的其它部分(类、函数或者其它)可以得到该事件已经发生的通知并对此做出相应的处理。

发布者-订阅者模式(publisher/subscriber pattern)可以满足这种需求。在这种模式中,发布者类定义了订阅者类感兴趣的事件。当事件发生时,发布者通知到这些订阅者,然后订阅者执行相应的事件处理函数。

但是,发布者是如何通知到这些订阅者呢?订阅者通过注册函数。其实就是发布者维护了一个关注某个事件的订阅者集合。例如,我在 csdn 博客上开了某个领域的专栏,然后你订阅了这个专栏,在后台会维护关注这个专栏的用户列表。当我更新了这个专栏的文章时,会通知到每一个订阅的用户。

那么,订阅者又是如何在事件发生时执行相应的事件处理函数呢?订阅者向发布者注册事件发生时的事件处理函数(回调函数,我们称函数的参数类型是函数的函数)。

下图说明了发布者-订阅者模式的工作流程:
在这里插入图片描述
下面是发布者-订阅者模式的组件:

  1. 发布者:发布某个事件的类或结构。维护一个订阅者集合(可选)、一个事件处理程序的集合、提供给订阅者注册订阅和回调函数的接口;
  2. 订阅者:关注事件的类或者结构。需要向订阅者提供回调函数名;
  3. 触发事件。本质上是触发事件的代码。

在 C#高级教程(一):委托当中介绍了委托。实际上,事件就像是专门用于某种特殊用途的委托。

2. 源代码组件

为了实现发布者-订阅者模式,在代码中需要完成以下 5 部分:

  • 委托类型声明:事件和事件处理程序必须具有相同的签名和返回类型,它们通过委托类型进行描述;
  • 事件处理程序声明:订阅者类中会在事件触发时执行的方法调用;
  • 事件声明:发布者类必须声明一个订阅者类可以注册的事件成员;
  • 事件注册:订阅者必须订阅事件才能在它被触发时得到通知;
  • 触发事件的代码:发布者类中“触发”事件并执行所有事件处理程序的代码。
    在这里插入图片描述
    下面是一个简单的代码实现:
delegate void Handler();

class Incrementer
{
    public event Handler CountedADozen;     // 事件名

    public void DoCount()
    {
        for (int i = 1; i < 100; i++)
        {
            if (i % 12 == 0 && CountedADozen!= null) // 触发事件的代码
            {
                CountedADozen();        // 调用事件
            }
        }
    }
}

// 订阅者
class Dozens
{
    public int DozensCount { get; set; }

    public Dozens(Incrementer incrementer)
    {
        DozensCount = 0;
        incrementer.CountedADozen += IncrementDozenCount;   // 注册回调函数
    }
    
    // 回调函数
    void IncrementDozenCount()
    {
        DozensCount++;
    }
}

class Programer
{
    static void Main()
    {
        Incrementer incrementer = new Incrementer();
        Dozens dozens = new Dozens(incrementer);

        incrementer.DoCount();
        Console.WriteLine("Dozens count: {0}", dozens.DozensCount);
    }
}

输出:
在这里插入图片描述

3. 标准事件的用法

由于 GUI 编程是事件驱动的,而 Windows GUI 编程广泛地使用了事件,因此 .NET 框架提供了一个标准模式。具体就是在 System 命名空间提供了 EventHandler 委托类型。
在这里插入图片描述
第二个参数 EventArgs 设计为不能用来传递任何数据,它用于不需要传递数据的事件处理程序。如果你希望传递数据,必须声明一个派生自 EventArgs 的类,使用合适的字段来保存需要传递的数据。

接下来,我们就使用标准事件改写第二节的程序:

class Incrementer
{
    public event EventHandler CountedADozen;     // 事件名

    public void DoCount()
    {
        for (int i = 1; i < 100; i++)
        {
            if (i % 12 == 0 && CountedADozen!= null) // 触发事件的代码
            {
                CountedADozen(this, null);        // 调用事件
            }
        }
    }
}
// 订阅者
class Dozens
{
    public int DozensCount { get; set; }

    public Dozens(Incrementer incrementer)
    {
        DozensCount = 0;
        incrementer.CountedADozen += IncrementDozenCount;   // 注册回调函数
    }
    
    // 回调函数
    void IncrementDozenCount(object sender, EventArgs e)
    {
        DozensCount++;
    }
}

class Programer
{
    static void Main()
    {
        Incrementer incrementer = new Incrementer();
        Dozens dozens = new Dozens(incrementer);

        incrementer.DoCount();
        Console.WriteLine("Dozens count: {0}", dozens.DozensCount);
    }
}

通过扩展 EventArgs 来传递数据

为了向自己的事件处理程序的第二个参数传入数据,并且又符合标准惯例,我们需要声明一个派生自 EventArgs 的自定义类,用来保存我们需要传入的数据。

下面是改写后的代码:

// 派生自EventArgs的自定义类
public class IncrementerArgs: EventArgs
{
    public int IterationCount { get; set; }
}

class Incrementer
{
    // 使用自定义类的泛型委托
    public event EventHandler<IncrementerArgs> CountedADozen;     // 事件名

    public void DoCount()
    {
        IncrementerArgs args = new IncrementerArgs();
        for (int i = 1; i < 100; i++)
        {
            if (i % 12 == 0 && CountedADozen!= null) // 触发事件的代码
            {
                args.IterationCount = i;
                CountedADozen(this, args);        // 调用事件
            }
        }
    }
}
// 订阅者
class Dozens
{
    public int DozensCount { get; set; }

    public Dozens(Incrementer incrementer)
    {
        DozensCount = 0;
        incrementer.CountedADozen += IncrementDozenCount;   // 注册回调函数
    }
    
    // 回调函数
    void IncrementDozenCount(object sender, IncrementerArgs e)
    {
        Console.WriteLine("Incremented at iteration: {0} and {1}",
                            e.IterationCount, sender.ToString());
        DozensCount++;
    }
}

class Programer
{
    static void Main()
    {
        Incrementer incrementer = new Incrementer();
        Dozens dozens = new Dozens(incrementer);

        incrementer.DoCount();
        Console.WriteLine("Dozens count: {0}", dozens.DozensCount);
    }
}

输出:
在这里插入图片描述

移除事件处理程序

在用完了事件处理程序之后,可以从事件中把它移除。下面是一个例子:

class Publisher
{
    public event EventHandler SimpleEvent;

    public void RaiseTheEvent() { SimpleEvent(this, null); }
}

class Subsriber
{
    public void MethodA(object o, EventArgs e) { Console.WriteLine("MethodA called"); }
    public void MethodB(object o, EventArgs e) { Console.WriteLine("MethodB called"); }

}

class Program
{
    static void Main()
    {
        Publisher p = new Publisher();
        Subsriber s = new Subsriber();

        p.SimpleEvent += s.MethodA;
        p.SimpleEvent += s.MethodB;
        p.RaiseTheEvent();

        Console.WriteLine("\r\nRemove MethodB");
        p.SimpleEvent -= s.MethodB;
        p.RaiseTheEvent();
    }
}

程序的输出:
在这里插入图片描述

小结:本章介绍了 C# 语言当中的发布者-订阅者模式的概念,源代码组件的五个部分,以及标准事件的用法。

各位道友,码字不易。如有收获,记得一键三连。


http://www.kler.cn/news/335901.html

相关文章:

  • 【汇编语言】寄存器(CPU工作原理)(一)—— 寄存器的基础知识及存储
  • 动态内存管理笔试题
  • 明星周边销售网站开发:SpringBoot技术全解析
  • 【C++】STL--vector
  • 【一起学NLP】Chapter3-使用神经网络解决问题
  • 大数据新视界 --大数据大厂之 Sqoop 在大数据导入导出中的应用与技巧
  • SQL专项练习第三天
  • 【电商搜索】现代工业级电商搜索技术-中科院计算机研究所-生成型检索与多级相关性相结合
  • 【web安全】——常见框架漏洞
  • SpringBoot实现:星之语明星周边销售平台开发指南
  • 基于深度学习的思维控制的设备
  • 21. 合并两个有序链表
  • 进程的环境
  • React生命周期案例详解
  • HarmonyOS第一课 04 应用程序框架基础-习题分析
  • SSM社区慢性病管理系统—计算机毕业设计源码37572
  • 数据结构 ——— 单链表oj题:反转链表
  • 滚雪球学Oracle[2.4讲]:创建Oracle数据库实例
  • Maven(6)如何使用Maven进行项目构建?
  • 图解网络OSI模型与TCP/IP