学懂C#编程:常用高级技术——委托(Delegate)应用场景——事件处理

该文章已生成可运行项目,

目录

引言

委托和事件

示例代码

事件处理示例解析

更详细地解读代码结构

Publisher 类

Subscriber 类

整体流程

事件处理的高级应用

1. 泛型事件

2. 异步事件

AsyncPublisher 类

AsyncSubscriber 类

总结


引言

在C#编程中,事件(Event)是一种特殊的委托类型,用于在对象上发生某些事情时通知订阅者。事件处理通常包括定义事件,创建触发事件的条件,以及订阅该事件的事件处理程序。事件机制让对象能够通知其他对象“某件事发生了”,而不用直接调用那些对象的方法,从而增强了代码的解耦和灵活性。

委托和事件

委托是C#事件模型的核心。一个事件本质上是一个委托类型的字段,用于存储一系列方法的引用,当特定事件发生时,这些方法会被调用。事件相当于对委托的进一步封装和限制,使得事件只能在声明它的类中触发,而不能在其他地方触发。

让我们通过一个完整的示例来深入了解C#中事件的基本用法。这个示例包括Publisher类(发布者)用于发布事件,Subscriber类(订阅者)用于订阅并处理这个事件。

示例代码

using System;

// 定义委托类型
public delegate void SimpleEventHandler(object sender, EventArgs e);

// 定义包含事件的类
public class SimpleEventSource
{
    // 声明事件
    public event SimpleEventHandler SimpleEvent;

    // 触发事件的方法
    public void RaiseEvent()
    {
        // 检查是否有方法订阅了事件
        if (SimpleEvent != null)
        {
            // 使用EventArgs的默认实例触发事件
            SimpleEvent(this, EventArgs.Empty);
        }
    }
}

// 定义事件处理程序
public class SimpleEventReceiver
{
    public void OnSimpleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("事件触发了!");
    }
}

public class Program
{
    public static void Main()
    {
        SimpleEventSource source = new SimpleEventSource();
        SimpleEventReceiver receiver = new SimpleEventReceiver();

        // 订阅事件
        source.SimpleEvent += receiver.OnSimpleEvent;

        // 触发事件
        source.RaiseEvent();
    }
}

事件处理示例解析

在这个示例中,我们首先定义了一个委托 SimpleEventHandler,然后定义了一个包含该事件的类 SimpleEventSource。在 SimpleEventSource 中,我们定义了一个方法 RaiseEvent 来触发事件。SimpleEventReceiver 类包含事件处理程序 OnSimpleEvent,当事件触发时,它会在控制台上打印一条消息。在 Main 方法中,我们创建了事件源和接收器的实例,并订阅了事件。最后,我们调用 RaiseEvent 方法来模拟发生了一个事件,事件触发后,事件处理程序被调用。

更详细地解读代码结构

Publisher 类

Publisher(发布者)就像是一个广播站,它可以发出某种信号或者说“事件”。在这个例子中,它有一个名为 EventHappened 的事件,这个事件的类型是 EventHandler<EventArgs>。你可以认为 EventHandler<EventArgs> 是一种特殊的委托类型,它连接了事件触发的地方和事件响应的地方。当事件触发时,所有订阅了这个事件的人都会得到通知。

public class Publisher
{
    // 声明一个事件,类型是 EventHandler,它是一个委托类型,用于接收发送者对象和事件参数
    public event EventHandler<EventArgs> EventHappened;

    // 这个方法用于触发事件,即告诉所有订阅者:“事件发生了!”
    public void RaiseEvent()
    {
        // 使用?.运算符来安全地调用事件,如果没有人订阅,也不会抛出异常
        EventHappened?.Invoke(this, EventArgs.Empty); 
        // this 表示事件发生的源头,EventArgs.Empty 是默认的事件参数,通常用于表示事件发生,但没有携带额外信息
    }
}

Subscriber 类

Subscriber(订阅者)就像是收音机,它对特定的广播(事件)感兴趣。在构造函数中,Subscriber订阅了来自PublisherEventHappened事件,并指定了当这个事件发生时应该执行的方法OnEvent

public class Subscriber
{
    // 构造函数中,订阅Publisher的EventHappened事件
    public Subscriber(Publisher pub)
    {
        // += 操作符用于订阅事件,OnEvent 是事件触发时执行的方法
        pub.EventHappened += OnEvent;
    }

    // 这是事件处理方法,当EventHappened事件被Publisher触发时,这个方法就会被执行
    private void OnEvent(object sender, EventArgs e)
    {
        // 打印一条消息,表明事件已被处理
        Console.WriteLine("Event handled.");
    }
}
整体流程
  1. 创建事件源:创建一个 Publisher 实例,它是事件的源头。
  2. 创建订阅者:创建一个或多个 Subscriber 实例,并将它们与 Publisher 关联起来。这个关联过程就是订阅事件,通过在 Subscriber 的构造函数中向 Publisher 的事件添加事件处理程序(OnEvent 方法)来实现。
  3. 触发事件:当 Publisher 通过调用 RaiseEvent 方法“广播”事件时,所有订阅了这个事件的 Subscriber 实例都会接收到通知,并执行它们各自定义的 OnEvent 方法,完成事件的处理。

简而言之,事件机制让对象能够通知其他对象“某件事发生了”,而不用直接调用那些对象的方法,增强了代码的解耦和灵活性。

事件处理的高级应用

1. 泛型事件

事件处理器可以使用泛型来处理更复杂的数据。举个例子:

using System;

// 定义一个泛型的事件参数类,继承自EventArgs
public class DataEventArgs<T> : EventArgs
{
    // 定义一个只读属性来存储事件数据
    public T Data { get; }

    // 构造函数,用于初始化事件数据
    public DataEventArgs(T data)
    {
        Data = data;
    }
}

// 定义一个发布者类,负责发布事件
public class AdvancedPublisher
{
    // 声明一个泛型事件,事件类型为EventHandler<DataEventArgs<int>>
    public event EventHandler<DataEventArgs<int>> DataProcessed;

    // 模拟处理数据的方法,这里传入一个int类型的数据
    public void ProcessData(int data)
    {
        // 处理数据的逻辑可以放在这里...
        // 数据处理完成后,触发事件
        OnDataProcessed(new DataEventArgs<int>(data));
    }

    // 受保护的虚方法,用于触发事件
    protected virtual void OnDataProcessed(DataEventArgs<int> e)
    {
        // 使用安全调用操作符 ?. 来调用事件,如果没有订阅者,不会产生异常
        DataProcessed?.Invoke(this, e);
    }
}

// 定义一个订阅者类,负责处理事件
public class AdvancedSubscriber
{
    // 构造函数,传入一个发布者实例,并订阅其DataProcessed事件
    public AdvancedSubscriber(AdvancedPublisher pub)
    {
        // 使用+=操作符订阅事件,并指定事件处理方法
        pub.DataProcessed += OnDataProcessed;
    }

    // 事件处理方法,当事件被触发时会执行该方法
    private void OnDataProcessed(object sender, DataEventArgs<int> e)
    {
        // 打印事件数据到控制台
        Console.WriteLine($"Data processed: {e.Data}");
    }
}

代码解析

  1. DataEventArgs<T> 类

    • 这是一个泛型的事件参数类,继承自 EventArgs。它包含一个只读属性 Data 用于存储事件数据。
    • 构造函数 DataEventArgs(T data) 用于初始化事件数据。
  2. AdvancedPublisher 类

    • 这是发布者类,负责处理数据并发布事件。
    • 声明了一个泛型事件 DataProcessed,事件类型为 EventHandler<DataEventArgs<int>>
    • ProcessData(int data) 方法用来模拟数据处理。数据处理完成后,通过调用 OnDataProcessed 方法来触发事件。
    • OnDataProcessed(DataEventArgs<int> e) 方法是受保护的虚方法,用于实际触发事件。使用安全调用操作符 ?. 来防止没有订阅者时调用事件引发的异常。
  3. AdvancedSubscriber 类

    • 这是订阅者类,负责订阅并处理发布者的事件。
    • 构造函数 AdvancedSubscriber(AdvancedPublisher pub) 接受一个发布者实例,并订阅其 DataProcessed 事件。
    • OnDataProcessed(object sender, DataEventArgs<int> e) 方法是事件处理方法,当事件被触发时会执行该方法。此方法将事件数据打印到控制台。

完整流程

  1. 创建 AdvancedPublisher 实例。
  2. 创建 AdvancedSubscriber 实例,并传入发布者实例。订阅者订阅了发布者的 DataProcessed 事件。
  3. 发布者调用 ProcessData(int data) 方法处理数据。
  4. ProcessData 方法在处理数据后,调用 OnDataProcessed 方法触发事件。
  5. 所有订阅了 DataProcessed 事件的订阅者都会接收到通知,并执行各自的事件处理方法 OnDataProcessed

 

2. 异步事件

事件处理器可以异步执行以提高响应性能:

using System;
using System.Threading.Tasks;

// 定义一个发布者类,负责发布异步事件
public class AsyncPublisher
{
    // 声明一个事件,类型为 EventHandler<EventArgs>
    public event EventHandler<EventArgs> DataReady;

    // 异步数据处理方法
    public async Task ProcessDataAsync()
    {
        // 模拟数据处理,延迟 1000 毫秒
        await Task.Delay(1000);
        // 处理完成后触发事件
        OnDataReady();
    }

    // 受保护的虚方法,用于触发事件
    protected virtual void OnDataReady()
    {
        // 使用安全调用操作符 ?. 来调用事件,如果没有订阅者,不会产生异常
        DataReady?.Invoke(this, EventArgs.Empty);
    }
}

// 定义一个订阅者类,负责处理异步事件
public class AsyncSubscriber
{
    // 构造函数,传入一个发布者实例,并订阅其 DataReady 事件
    public AsyncSubscriber(AsyncPublisher pub)
    {
        // 使用 += 操作符订阅事件,并指定事件处理方法
        pub.DataReady += async (sender, e) => await HandleDataReadyAsync();
    }

    // 异步事件处理方法,当事件被触发时会执行该方法
    private async Task HandleDataReadyAsync()
    {
        // 模拟异步数据处理,延迟 500 毫秒
        await Task.Delay(500);
        // 打印处理完成消息到控制台
        Console.WriteLine("Data processed asynchronously");
    }
}

代码解析

AsyncPublisher 类
  1. 事件声明

    • public event EventHandler<EventArgs> DataReady;
    • 声明一个事件 DataReady,类型为 EventHandler<EventArgs>。这个事件用于通知订阅者数据处理已完成。
  2. 异步数据处理方法

    • public async Task ProcessDataAsync()
    • 这是一个异步方法,使用 async 和 Task 关键字声明。
    • 在方法内部使用 await Task.Delay(1000) 模拟数据处理的延迟。Task.Delay(1000) 会创建一个任务并延迟 1000 毫秒(1 秒)。
  3. 触发事件的方法

    • protected virtual void OnDataReady()
    • 这是一个受保护的虚方法,用于触发 DataReady 事件。
    • 使用 DataReady?.Invoke(this, EventArgs.Empty) 安全地调用事件,确保在没有订阅者时不会抛出异常。EventArgs.Empty 表示事件没有携带额外信息。
AsyncSubscriber 类
  1. 构造函数

    • public AsyncSubscriber(AsyncPublisher pub)
    • 构造函数接收一个 AsyncPublisher 实例 pub,并订阅其 DataReady 事件。
  2. 订阅事件

    • pub.DataReady += async (sender, e) => await HandleDataReadyAsync();
    • 使用 += 操作符订阅事件,并指定一个异步事件处理方法 HandleDataReadyAsync
    • async (sender, e) => await HandleDataReadyAsync() 是一个异步 lambda 表达式,当事件触发时会执行 HandleDataReadyAsync 方法。
  3. 异步事件处理方法

    • private async Task HandleDataReadyAsync()
    • 这是一个异步方法,用于处理事件。
    • 在方法内部使用 await Task.Delay(500) 模拟异步数据处理的延迟。Task.Delay(500) 会创建一个任务并延迟 500 毫秒(0.5 秒)。
    • 数据处理完成后,打印一条消息 "Data processed asynchronously" 到控制台。

完整流程

  1. 创建 AsyncPublisher 实例。
  2. 创建 AsyncSubscriber 实例,并传入发布者实例。订阅者订阅了发布者的 DataReady 事件。
  3. 发布者调用 ProcessDataAsync 方法异步处理数据。
  4. ProcessDataAsync 方法在处理数据后,通过 OnDataReady 方法触发事件。
  5. 所有订阅了 DataReady 事件的订阅者都会接收到通知,并异步执行 HandleDataReadyAsync 方法,完成事件的处理。

 

总结

事件处理是C#中非常重要的机制,通过事件,程序可以实现松耦合的设计,使得代码更加灵活和可维护。事件的核心是委托,通过委托将事件与事件处理程序连接起来。事件可以用来通知订阅者对象某些事情的发生,而不需要直接调用这些对象的方法。

主要特点:

  1. 解耦:事件机制使得发布者和订阅者之间松耦合。
  2. 灵活性:可以动态添加或移除事件处理程序。
  3. 异步处理:事件处理程序可以异步执行,提高响应性能。
  4. 扩展性:通过泛型和自定义事件参数,处理复杂的数据和逻辑。

重要应用场景:

  1. UI编程:事件驱动的编程模型使得用户界面响应用户操作。
  2. 异步编程:事件处理程序可以用于处理异步操作的完成通知。
  3. 组件通信:不同组件之间通过事件进行通信。

通过合理地使用事件机制,可以有效地提高代码的可读性、可维护性和灵活性。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿享天开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值