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

C#中的事件、代理与任务:深入剖析发布者 - 订阅者模式中的关键元素

文章目录

    • 一、引言
    • 二、事件、代理和任务的基本概念
    • 三、发布者与订阅者的角色与关系
    • 四、代理和事件在订阅和发布上的异同
      • 1. 相同点
      • 2. 不同点
    • 五、触发方法与订阅者处理方法
      • 1. 触发方法
      • 2. 订阅者处理方法
    • 六、任务的订阅和发布代码示例
    • 七、事件和代理综合代码示例
      • 事件部分
      • 代理部分
    • 七、总结

原文出处: https://blog.csdn.net/haigear/article/details/142684587

一、引言

在现代编程范式中,事件(Event)和代理(Delegate)是构建松耦合、可扩展系统的重要机制,特别是在实现发布者 - 订阅者模式时发挥着关键作用。此外,任务(Task)概念的引入为异步操作提供了便捷的处理方式。本文将深入探讨事件、代理和任务的内涵,着重解析发布者和订阅者的关系,并详细阐述代理和事件在订阅和发布操作上的异同之处,同时通过代码示例进行直观展示。

二、事件、代理和任务的基本概念

  1. 事件(Event)
    • 事件是一种对象间通信的机制,它与特定的对象状态变化或操作相关联。在面向对象编程中,事件是类的成员,当特定条件满足时被触发,用于通知其他对象发生了某些感兴趣的事情。
    • 事件的定义基于委托类型,委托类型规定了事件处理方法的签名,包括返回值类型和参数列表。事件处理方法是在事件被触发时执行相应操作的方法。
  2. 代理(Delegate)
    • 代理本质上是一种类型安全的函数指针,它定义了方法的签名。通过代理,可以将方法作为参数传递给其他方法,实现方法的间接调用,这种特性在实现回调机制时非常有用。
    • 在发布者 - 订阅者模式中,代理定义了订阅者需要实现的方法签名,以便能够订阅发布者发出的通知。
  3. 任务(Task)
    • 任务用于表示异步操作,它允许程序在不阻塞主线程的情况下执行耗时操作,如文件 I/O、网络请求等。在事件处理场景中,任务可以与事件或代理结合,使事件处理方法以异步方式执行。

三、发布者与订阅者的角色与关系

  1. 发布者(Publisher)
    • 发布者是事件的源头,负责检测特定条件并触发事件。它包含事件成员变量(对于事件)或代理变量(对于代理),这些变量用于存储订阅者注册的事件处理方法引用。
    • 发布者提供触发事件或调用代理的方法。例如,在特定的业务逻辑执行完毕或者状态发生改变时,发布者会执行这些方法,从而通知订阅者。
    • 此外,发布者还可能提供管理订阅者的功能,尽管在某些语言特性下这种管理可能是隐式的。
  2. 订阅者(Subscriber)
    • 订阅者是对发布者发出的事件感兴趣的对象。它实现了与代理定义的签名相匹配的方法,这些方法包含了在接收到事件通知时要执行的逻辑。
    • 订阅者通过向发布者注册事件处理方法来表达对事件的关注。注册过程建立了发布者与订阅者之间的联系,使得在事件触发时订阅者能够接收到通知。

四、代理和事件在订阅和发布上的异同

1. 相同点

- **模式实现**:两者均为实现发布者 - 订阅者模式的重要手段,通过这种模式,发布者和订阅者之间可以实现低耦合的通信。发布者不需要知道订阅者的具体类型,只需要按照定义好的委托签名(对于代理)或事件处理签名(对于事件)来调用订阅者的方法。
- **签名定义**:都依赖于方法签名的定义。代理明确地定义了一个方法签名,而事件基于委托类型,这个委托类型也定义了事件处理方法的签名。这使得订阅者能够按照统一的标准提供事件处理方法。
- **通知机制**:在事件触发(对于事件)或代理调用(对于代理)时,都会通知已注册的订阅者,并且执行相应的订阅者处理方法。
- 示例代码:
// 定义委托类型,与事件处理方法签名相同
public delegate void MyEventHandler(object sender, EventArgs e);

// 发布者类(用于展示事件和代理的相同点)
class PublisherForSimilarity
{
    // 对于事件,可以定义事件成员变量
    public event MyEventHandler MyEvent;

    // 对于代理,可以定义代理变量
    public MyEventHandler MyDelegate;

    public void TriggerOrCall()
    {
        if (MyEvent!= null)
        {
            MyEvent(this, EventArgs.Empty);
        }
        if (MyDelegate!= null)
        {
            MyDelegate(this, EventArgs.Empty);
        }
    }
}

// 订阅者类(用于展示事件和代理的相同点)
class SubscriberForSimilarity
{
    public void HandleEventOrDelegate(object sender, EventArgs e)
    {
        Console.WriteLine("Handled by subscriber for similarity.");
    }
}

2. 不同点

  • 触发与调用的限制
    • 事件:事件是一种特殊的成员,只能在定义它的类内部触发。这意味着事件的触发逻辑被限制在特定的类范围内,增强了封装性。例如,在 C#中,事件只能由包含该事件的类中的方法触发,外部类无法直接触发事件。
    • 代理:代理可以在任何具有代理实例引用的地方被调用,没有类范围的限制。这使得代理在使用上更加灵活,但也需要更多的谨慎来确保正确的调用逻辑。
    • 示例代码:
// 发布者类(用于展示事件和代理触发限制的不同)
class PublisherForLimit
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }

    public MyEventHandler MyDelegate;

    public void CallDelegate()
    {
        MyDelegate?.Invoke(this, EventArgs.Empty);
    }
}
  • 订阅者管理
    • 事件:事件通常由语言特性自动维护订阅者列表。在 C#中,使用 +=-= 运算符来添加和移除订阅者时,背后的订阅者列表管理是自动进行的。例如,当使用 publisher.MyEvent += subscriber.HandleEvent; 时,系统会自动将订阅者的方法添加到事件的订阅者列表中,无需手动干预。
    • 代理:代理本身并不自动维护订阅者列表。如果要实现类似事件的多订阅者功能,开发者需要手动创建和维护一个订阅者列表(通常是一个数据结构,如列表或数组)来存储订阅者的方法引用,并在调用代理时遍历这个列表来依次调用每个订阅者的方法。这增加了开发的复杂性,但也提供了更多的定制性。
      • 示例代码:
// 使用事件实现发布者 - 订阅者模式的发布者类
class PublisherWithEvent
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

// 使用代理实现发布者 - 订阅者模式的发布者类,并手动维护订阅者列表
class PublisherWithDelegate
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public void InvokeDelegate()
    {
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}
  • 语法与操作符
    • 事件:在 C#等语言中,事件有特定的语法和操作符用于订阅和取消订阅操作。除了前面提到的 +=-= 操作符外,事件在定义时也有特定的语法格式,如 public event EventHandler MyEvent;。这种语法明确地标识了这是一个事件成员,并且对事件的操作受到语言规则的约束。
    • 代理:代理的操作更像是普通的方法调用和赋值操作。订阅者将自己的方法赋值给代理变量(例如 publisher.MyDelegate = subscriber.HandleEvent;),虽然也可以实现类似事件的多订阅者功能(通过手动维护列表并在调用时遍历列表中的代理方法),但语法上没有像事件那样专门针对订阅和取消订阅的操作符。
      • 示例代码:
// 使用事件进行订阅和触发
class EventSubscriptionExample
{
    PublisherWithEvent publisher = new PublisherWithEvent();
    SubscriberWithEvent subscriber = new SubscriberWithEvent();

    public void EventSubscription()
    {
        publisher.MyEvent += subscriber.HandleEvent;
        publisher.TriggerEvent();
    }
}

// 使用代理进行赋值和调用(单个代理的情况,不涉及多订阅者管理)
class DelegateAssignmentExample
{
    PublisherWithDelegate publisher = new PublisherWithDelegate();
    SubscriberWithDelegate subscriber = new SubscriberWithDelegate();

    public void DelegateAssignment()
    {
        publisher.myDelegate = subscriber.HandleEvent;
        publisher.myDelegate?.Invoke(publisher, EventArgs.Empty);
    }
}

原文出处:https://blog.csdn.net/haigear/article/details/142684587

五、触发方法与订阅者处理方法

1. 触发方法

  • 对于发布者而言,触发方法是引发事件通知或调用代理的方法。在事件的情况下,触发方法内部会检查事件是否有订阅者(通过检查事件是否为 null),如果有,则调用所有订阅者的事件处理方法。在代理的情况下,触发方法(如果是自定义的)会遍历手动维护的订阅者列表(如果有),然后依次调用每个订阅者的方法。
    • 示例代码:
// 使用事件实现发布者 - 订阅者模式的发布者类
class PublisherWithEvent
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

// 使用代理实现发布者 - 订阅者模式的发布者类,并手动维护订阅者列表
class PublisherWithDelegate
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public void InvokeDelegate()
    {
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}

2. 订阅者处理方法

  • 订阅者处理方法是由订阅者定义的方法,其签名必须与代理定义的签名(对于代理)或事件基于的委托类型的签名(对于事件)相匹配。当事件被触发或代理被调用时,发布者会将相关参数传递给订阅者处理方法,订阅者根据这些参数执行特定的逻辑操作。
    • 示例代码:
// 订阅者类(用于事件和代理)
class SubscriberForBoth
{
    public void HandleEventOrDelegate(object sender, EventArgs e)
    {
        Console.WriteLine("Handled by subscriber.");
    }
}

六、任务的订阅和发布代码示例

以下是一个使用任务结合事件或代理的示例:


using System;
using System.Threading.Tasks;

// 定义委托类型,与事件处理方法签名相同
public delegate void MyEventHandler(object sender, EventArgs e);

// 使用事件结合任务的发布者类
class PublisherWithEventAndTask
{
    public event MyEventHandler MyEvent;

    public async Task TriggerEventWithTask()
    {
        await Task.Delay(1000);
        if (MyEvent!= null)
        {
            MyEvent(this, EventArgs.Empty);
        }
    }
}

// 使用代理结合任务的发布者类,并手动维护订阅者列表
class PublisherWithDelegateAndTask
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public async Task InvokeDelegateWithTask()
    {
        await Task.Delay(1000);
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}

class SubscriberWithTask
{
    public void HandleEventOrDelegate(object sender, EventArgs e)
    {
        Console.WriteLine("Handled by subscriber with task.");
    }
}

你可以这样使用这些类:


class Program
{
    static void Main()
    {
        // 使用事件结合任务的示例
        PublisherWithEventAndTask eventPublisherWithTask = new PublisherWithEventAndTask();
        SubscriberWithTask eventSubscriberWithTask = new SubscriberWithTask();

        eventPublisherWithTask.MyEvent += eventSubscriberWithTask.HandleEventOrDelegate;

        eventPublisherWithTask.TriggerEventWithTask().Wait();

        // 使用代理结合任务的示例
        PublisherWithDelegateAndTask delegatePublisherWithTask = new PublisherWithDelegateAndTask();
        SubscriberWithTask delegateSubscriberWithTask = new SubscriberWithTask();

        delegatePublisherWithTask.AddSubscriber(delegateSubscriberWithTask.HandleEventOrDelegate);

        delegatePublisherWithTask.InvokeDelegateWithTask().Wait();
    }
}

七、事件和代理综合代码示例

以下是一个 C#代码示例,用于综合展示事件和代理在不同情况下的使用:

using System;
using System.Collections.Generic;

// 1. 定义委托类型,与事件处理方法签名相同
public delegate void MyEventHandler(object sender, EventArgs e);
public delegate double Cal(double x, double y);

// 使用事件实现发布者 - 订阅者模式
class PublisherWithEvent
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

class SubscriberWithEvent
{
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by subscriber with event.");
    }
}

// 使用代理实现发布者 - 订阅者模式,并手动维护订阅者列表
class PublisherWithDelegate
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public void InvokeDelegate()
    {
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}

class SubscriberWithDelegate
{
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by subscriber with delegate.");
    }
}

// 类似 Cal cal = new Cal(Add) 的代理实例化示例
class CalDelegateExample
{
    public double Calculate(Cal cal, double x, double y)
    {
        return cal(x, y);
    }
}

class Program
{
    static void Main()
    {
        // 使用事件的示例
        PublisherWithEvent publisherWithEvent = new PublisherWithEvent();
        SubscriberWithEvent subscriberWithEvent1 = new SubscriberWithEvent();
        SubscriberWithEvent subscriberWithEvent2 = new SubscriberWithEvent();

        publisherWithEvent.MyEvent += subscriberWithEvent1.HandleEvent;
        publisherWithEvent.MyEvent += subscriberWithEvent2.HandleEvent;

        publisherWithEvent.TriggerEvent();

        // 使用代理的示例
        PublisherWithDelegate publisherWithDelegate = new PublisherWithDelegate();
        SubscriberWithDelegate subscriberWithDelegate1 = new SubscriberWithDelegate();
        SubscriberWithDelegate subscriberWithDelegate2 = new SubscriberWithDelegate();

        publisherWithDelegate.AddSubscriber(subscriberWithDelegate1.HandleEvent);
        publisherWithDelegate.AddSubscriber(subscriberWithDelegate2.HandleEvent);

        publisherWithDelegate.InvokeDelegate();

        // 类似 Cal cal = new Cal(Add) 的代理实例化示例
        CalDelegateExample calExample = new CalDelegateExample();
        Cal cal = new Cal(Add);
        Console.WriteLine(calExample.Calculate(cal, 5, 3));

        cal = new Cal(Subtract);
        Console.WriteLine(calExample.Calculate(cal, 5, 3));
    }

    static double Add(double x, double y)
    {
        return x + y;
    }

    static double Subtract(double x, double y)
    {
        return x - y;
    }
}

在上述代码示例中:

事件部分

  • PublisherWithEvent 类中,定义了事件 MyEvent。通过 += 操作符,轻松地将两个订阅者(subscriberWithEvent1subscriberWithEvent2)的事件处理方法注册到事件中。当调用 TriggerEvent 方法时,事件系统自动调用所有注册的订阅者方法,无需手动维护订阅者列表。

代理部分

  • PublisherWithDelegate 类中,为了实现类似事件的多订阅者功能,手动创建了一个 List<MyEventHandler> 类型的订阅者列表 subscriberList。通过 AddSubscriberRemoveSubscriber 方法来管理订阅者。在 InvokeDelegate 方法中,需要手动遍历订阅者列表来调用每个订阅者的方法。这清晰地展示了代理与事件在订阅者管理方面的差异。
  • 类似 Cal cal = new Cal(Add) 的代理实例化示例展示了代理作为简单的函数指针替代的用法,这里只是将一个符合委托签名的方法实例化到委托变量中,没有涉及多订阅者管理等复杂逻辑。

原文出处:https://blog.csdn.net/haigear/article/details/142684587

七、总结

事件和代理在发布者 - 订阅者模式的实现中各有特点。事件提供了一种更加封装、自动管理订阅者的方式,适用于大多数常见的发布 - 订阅场景;而代理则给予开发者更多的控制权,虽然需要手动处理一些逻辑,但在某些特定需求下(如需要高度定制订阅者管理逻辑)能够发挥优势。理解它们在订阅和发布操作上的异同,有助于根据实际项目需求选择合适的机制来构建灵活、可维护的软件系统。同时,任务的引入为事件和代理的异步操作提供了更多可能性,进一步丰富了程序设计的手段。对于代理的不同用法,从简单的类似函数指针的实例化到手动维护订阅者列表以实现发布者 - 订阅者模式,开发者可以根据具体情况进行选择和运用。


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

相关文章:

  • Leetcode 1498. 满足条件的子序列数目
  • 13:URL输入到页面渲染过程
  • LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
  • Nginx技术深度解析与实战应用
  • 通信工程学习:什么是RARP反向地址解析协议
  • 【笔记】信度检验
  • 令牌主动失效机制范例(利用redis)注释分析
  • 系统规划与管理——1信息系统综合知识(5)
  • 联想电脑怎么开启vt_联想电脑开启vt虚拟化教程(附intel和amd主板开启方法)
  • 蓝牙定位的MATLAB仿真程序(基于信号强度,平面内的定位,四个蓝牙基站)
  • 鸿蒙OpenHarmony
  • 懒人笔记-QT程序UOS打包篇
  • 105页PPT麦肯锡:煤炭贸易企业业务战略规划方案
  • 查看 Ubuntu 系统中是否安装了 Conda
  • 大学生就业招聘:Spring Boot系统的架构分析
  • 如何在 SQL 中创建一个新的数据库?
  • 【数据结构】【链表代码】 链表的中间节点
  • 融媒体服务中PBO进行多重采样抗锯齿(MSAA)
  • JAVA智慧社区系统跑腿家政本地生活商城系统小程序源码
  • 项目-坦克大战学习笔记-控制玩家坦克不超出地图范围