C#——委托与事件(二)

二、事件详解

事件是基于委托实现的高级机制,常用于发布-订阅模式。它是一种编程方式,让一个对象(发布者)可以通知其他对象(订阅者)某些事情发生。事件最常见的应用场景是
GUI 编程和消息广播。

下面,我们从基础概念开始,结合代码示例,详细说明事件的概念、使用和工作机制。

1. 什么是事件?

事件是对委托的封装,它将方法的调用权交给事件发布者,只有事件发布者才能触发事件,而订阅者可以注册要调用的方法。

事件的核心特点
1.只能触发者调用:外部对象不能直接调用事件,只能注册或取消订阅。
2.基于委托:事件的底层是委托。
3.多播机制:一个事件可以通知多个订阅者。

2. 声明和使用事件

代码示例:按钮点击事件
假设我们模拟一个简单的按钮点击事件:

using System;

class Program
{
    // 定义一个委托作为事件的签名
    public delegate void ClickHandler(string message);

    // 声明一个事件
    public event ClickHandler ButtonClicked;

    static void Main(string[] args)
    {
        var program = new Program();

        // 订阅事件
        program.ButtonClicked += msg => Console.WriteLine($"Subscriber 1 received: {msg}");
        program.ButtonClicked += msg => Console.WriteLine($"Subscriber 2 received: {msg}");

        // 模拟按钮点击,触发事件
        program.OnButtonClick("Button was clicked!");
    }

    // 触发事件的方法
    public void OnButtonClick(string message)
    {
        ButtonClicked?.Invoke(message); // 触发事件,调用订阅者的方法
    }
}

执行流程:
1.ButtonClicked 是一个事件,它可以被订阅(+=)或取消订阅(-=)。
2.当调用 OnButtonClick 方法时,会触发 ButtonClicked 事件。
3.所有订阅了 ButtonClicked 的方法都会被依次调用。

输出:

Subscriber 1 received: Button was clicked!
Subscriber 2 received: Button was clicked!

事件执行流程详解
1. 委托定义

public delegate void ClickHandler(string message);
  • 定义了一个委托类型 ClickHandler。
  • 它表示一个方法签名:接收一个 string 参数,无返回值。
  • 用于指定事件的处理方法的签名,所有订阅事件的方法必须符合这个签名。

2. 事件声明

public event ClickHandler ButtonClicked;
  • event 关键字修饰了 ButtonClicked,表明它是一个事件。
  • ButtonClicked 使用 ClickHandler 委托类型,表示它只能绑定符合该委托签名的方法。
  • 事件的本质:一个多播委托,但只能通过类内部触发,而不能在外部直接调用。
    • 订阅者(外部):只能使用 += 或 -= 添加或移除方法。
    • 发布者(类内部):只能使用 Invoke 方法触发事件。

3. 订阅事件

program.ButtonClicked += msg => Console.WriteLine($"Subscriber 1 received: {msg}");
program.ButtonClicked += msg => Console.WriteLine($"Subscriber 2 received: {msg}");
  • 使用 Lambda 表达式 订阅事件:
    • 第一个订阅者输出 Subscriber 1 received: {msg}。
    • 第二个订阅者输出 Subscriber 2 received: {msg}。
  • += 用于将方法添加到 ButtonClicked 的订阅列表中。
  • 订阅者的意义:
    • 这些方法在事件触发时会依次被调用。

4. 触发事件

public void OnButtonClick(string message)
{
    ButtonClicked?.Invoke(message); // 触发事件,调用所有订阅者
}
  • 触发逻辑:
    1.ButtonClicked 是一个事件,多播委托的封装。
    2.ButtonClicked?.Invoke(message) 检查事件是否有订阅者(避免空引用异常)。
    3.如果有订阅者,依次调用每个订阅的方法,参数 message 被传递给订阅方法。
  • 事件触发者的意义:
    • 在类的内部,事件发布者负责调用事件,通知所有订阅者。
    • 在这个例子中,事件通过 OnButtonClick 模拟按钮点击来触发。

5. 模拟事件触发

program.OnButtonClick("Button was clicked!");
  • 调用 OnButtonClick 方法触发事件。
  • 所有订阅了 ButtonClicked 的方法会依次被调用。
  • 输出结果:
Subscriber 1 received: Button was clicked!
Subscriber 2 received: Button was clicked!

执行流程总结

  1. 定义了一个事件 ButtonClicked,基于 ClickHandler 委托。
  2. 外部(订阅者)通过 += 订阅了事件。
  3. 当 OnButtonClick 方法被调用时,事件 ButtonClicked 被触发,通知所有订阅者。
  4. 所有订阅方法被依次调用,处理事件的消息。

事件的作用和特点
1.松耦合:

  • 事件机制实现了发布者和订阅者之间的松耦合,发布者无需了解订阅者的具体实现。

2.动态扩展:

  • 可以随时通过 += 添加新的订阅者,或通过 -= 移除订阅者。

3.线程安全:

  • 使用事件的 Invoke 调用方式,线程安全(如果有空值检查 ?.)。

4.实际应用场景:

  • 按钮点击、鼠标移动、数据加载完成等 GUI 应用程序中的典型事件处理场景。

3. 事件的发布-订阅模型

发布者和订阅者

  • 发布者:声明事件,并在特定条件下触发事件(如按钮点击)。
  • 订阅者:注册对事件的响应逻辑。

示例:多个订阅者响应一个事件

using System;

class Program
{
    public delegate void Notify(string message);
    public event Notify OnEventHappened;

    static void Main(string[] args)
    {
        var program = new Program();

        // 订阅者1
        program.OnEventHappened += msg => Console.WriteLine($"Logger: {msg}");
        
        // 订阅者2
        program.OnEventHappened += msg => Console.WriteLine($"Notifier: {msg}");

        // 发布者触发事件
        program.TriggerEvent("Something happened!");
    }

    public void TriggerEvent(string message)
    {
        Console.WriteLine("Event is about to trigger...");
        OnEventHappened?.Invoke(message); // 触发事件
    }
}

输出:

Event is about to trigger...
Logger: Something happened!
Notifier: Something happened!

4. 事件的语法简化

从 C# 2.0 开始,可以使用内置委托 Action 或 Func,简化事件的声明和使用。

简化代码:

using System;

class Program
{
    // 使用内置委托声明事件
    public event Action<string> OnMessage;

    static void Main(string[] args)
    {
        var program = new Program();

        // 订阅事件
        program.OnMessage += msg => Console.WriteLine($"Received: {msg}");

        // 触发事件
        program.TriggerEvent("Hello, simplified events!");
    }

    public void TriggerEvent(string message)
    {
        OnMessage?.Invoke(message);
    }
}

5. 常见应用场景

1. GUI 编程

  • 例如,按钮点击事件(Click)、文本改变事件(TextChanged)。

示例:WPF 中的事件

<Button Content="Click Me" Click="Button_Click" />

对应的 C# 代码:

private void Button_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Button was clicked!");
}

2. 数据变化通知

  • 例如 INotifyPropertyChanged 接口,用于通知 UI 数据的变化(MVVM 模式中常见)。
    示例:数据绑定和事件
using System;
using System.ComponentModel;

class ViewModel : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged("Name");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

6. 事件的优缺点

优点
1.解耦:发布者和订阅者之间没有强依赖关系。
2.灵活:可以动态添加或移除订阅者。
3.可扩展性强:多个订阅者可同时监听一个事件。
缺点
1.调试复杂:特别是多播事件,订阅者过多时不容易追踪调用链。
2.内存泄漏风险:事件订阅者没有正确移除订阅,可能导致内存泄漏。

总结
事件是委托的高级封装,核心在于解耦和发布-订阅模式。通过事件机制,程序的逻辑更加清晰、模块化,可以轻松应对动态变化的需求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值