C# 事件使用详解

server/2025/3/14 23:01:20/

总目录


前言

在C#中,事件(Events)是一种基于委托的重要机制,用于实现对象之间的松耦合通信。它通过发布-订阅模式(Publisher-Subscriber Pattern),允许一个对象(发布者)在特定条件发生时通知其他对象(订阅者)执行相应操作。事件是构建响应式、动态应用程序的核心工具,广泛应用于UI交互、游戏开发、网络通信等领域。本文将全面详细地介绍C#事件的使用,包括事件的定义、发布、订阅、取消订阅以及事件的高级用法。


一、什么是事件?

1. 定义

事件是一种特殊的委托(Delegate),用于通知其他对象某个状态或操作的发生。它通过委托实现,但提供了更安全的封装机制。

本质上 事件就是委托,如果说委托是对方法的包装,那么事件就是对委托进一步的包装,提升了使用的安全性,事件是安全版的委托

2. 定义语法

在 C# 中,事件的定义语法如下:

public event DelegateType EventName;
  • DelegateType:委托类型,定义了事件的签名。
  • EventName:事件的名称。

3. 完整生命周期

定义委托类型
声明事件
订阅事件
触发事件
执行处理程序

4. 关键术语

事件是 C# 中实现发布-订阅模式的核心机制,允许对象在特定动作发生时通知其他对象,实现松耦合的通信。 相关关键术语如下:

  • 发布者(Publisher):定义事件并触发事件的对象,负责通知事件的发生,如按钮控件。

  • 订阅者(Subscriber):订阅(监听)事件并定义事件处理逻辑的对象,当事件触发时执行相应操作。

  • 事件处理程序(Event Handler):订阅者注册的方法,用于处理事件触发后的逻辑。

  • 事件参数(EventArgs):传递事件相关数据的载体。

  • 触发(Raise)事件:发布者调用事件,通知所有订阅者执行其处理程序。

5. 理解发布订阅模式

现实世界的类比,想象一个报社订阅系统:

  • 报社(发布者):定期发布报纸,无需关心订阅者的具体身份;
  • 读者(订阅者):通过订阅获得报纸,根据内容采取行动(如阅读、剪报)。
  • 这种“一对多”的通信模式,正是事件的核心—发布-订阅模式(Publish-Subscribe Pattern)。

6. 事件与委托的关系

  • 委托定义了事件处理方法的签名,类似于函数指针。
  • 事件:安全封装的委托
  • 事件(Event):基于委托的封装,通过 event 关键字实现,是安全版委托。原因如下:
    • 外部不可直接调用和触发事件:仅发布者类可触发事件(event?.Invoke());
    • 订阅/取消订阅控制:外部只允许通过 +=-= 操作管理订阅者列表。
特性委托(Delegate)事件(Event)
定义语法通过 delegate 关键字定义,并可以直接调用。通过 event 关键字定义,并且只能在类内部触发事件。
访问权限外部代码可以直接调用委托。通过 event 关键字封装,外部只能通过 +=-= 操作。
安全性无封装,可能存在被滥用的风险。提供更安全的封装,避免外部随意修改委托链。
多播支持支持多播,可直接添加或移除方法。内部基于委托实现多播,但对外部隐藏委托实例。
设计目的通用方法引用的封装。专为发布-订阅模式设计,实现对象间解耦。
使用场景需要灵活调用不同方法的场景,
例如回调函数、异步编程等。
需要发布-订阅模式的场景,
例如用户界面交互、状态变化通知等。

7. 事件的基本认识

1)事件的定义

事件是基于委托的一种机制,用于在对象之间传递消息。要定义一个事件,首先需要定义一个委托类型,然后使用event关键字来声明事件。

public delegate void EventHandler(object sender, EventArgs e);
public class EventPublisher
{public event EventHandler MyEvent;
}

2)事件的发布

事件的发布是指在特定条件下触发事件,通知所有订阅者。这通常通过调用事件的Invoke方法来实现。

public class EventPublisher
{public event EventHandler MyEvent;public void RaiseEvent(){MyEvent?.Invoke(this, EventArgs.Empty);}
}

3)事件的订阅

事件的订阅是指订阅者注册到事件上,以便在事件触发时接收通知。这通常通过将一个方法传递给事件来实现。

public class EventSubscriber
{public void OnEvent(object sender, EventArgs e){Console.WriteLine("Event received!");}
}
public class Program
{public static void Main(){EventPublisher publisher = new EventPublisher();EventSubscriber subscriber = new EventSubscriber();publisher.MyEvent += subscriber.OnEvent; // 订阅事件publisher.RaiseEvent(); // 触发事件}
}

4)事件的取消订阅

事件的取消订阅是指订阅者从事件中注销,不再接收通知。这通常通过使用-=运算符来实现。

public class Program
{public static void Main(){EventPublisher publisher = new EventPublisher();EventSubscriber subscriber = new EventSubscriber();publisher.MyEvent += subscriber.OnEvent; // 订阅事件publisher.RaiseEvent(); // 触发事件publisher.MyEvent -= subscriber.OnEvent; // 取消订阅事件publisher.RaiseEvent(); // 不再触发事件}
}

二、如何使用事件

1. 自定义事件

1) 使用示例

using System;// 1. 委托定义(签名规范)
public delegate void MessageHandler(string msg);// Sender 类负责发布消息
public class Sender
{// 2. 事件声明public event MessageHandler OnMessageReceived;// 3. 事件触发方法protected virtual void RaiseEvent(string message){OnMessageReceived?.Invoke(message);}// 发送消息:在发送消息时调用 RaiseEvent 来触发事件。public void SendMessage(string message){Console.WriteLine($"Publisher: Sending message '{message}'");RaiseEvent(message);}
}// Subscriber 类负责接收和处理消息
public class Receiver
{private string _name;public Receiver(string name){_name = name;}public void HandleMessage(string message){Console.WriteLine($"{_name} received message: {message}");}
}class Program
{static void Main(string[] args){// 创建 Sender 实例var sender = new Sender();// 创建多个 Receiver 实例var receiver1 = new Receiver("Receiver 1");var receiver2 = new Receiver("Receiver 2");// 订阅事件sender.OnMessageReceived += receiver1.HandleMessage;sender.OnMessageReceived += receiver2.HandleMessage;// 发送消息并触发事件sender.SendMessage("Hello, World!");/*输出:Publisher: Sending message 'Hello, World!'Receiver 1 received message: Hello, World!Receiver 2 received message: Hello, World!    */sender.SendMessage("Another message!");/*输出:Publisher: Sending message 'Another message!'Receiver 1 received message: Another message!Receiver 2 received message: Another message!*/// 取消订阅某个事件(可选)sender.OnMessageReceived -= receiver1.HandleMessage;// 再次发送消息并触发事件sender.SendMessage("This message will not reach Receiver 1");/*输出:Publisher: Sending message 'This message will not reach Receiver 1'Receiver 2 received message: This message will not reach Receiver 1*/}
}

2)自定义事件使用步骤

以上示例展示了如何定义委托、声明事件、触发事件以及如何订阅和处理事件。下面拆解一下自定义事件的使用步骤。

1. 委托定义

我们定义了一个名为 MessageHandler 的委托类型,它接受一个字符串参数 msg 并返回 void

public delegate void MessageHandler(string msg);
2. 事件声明

Sender 类中,我们声明了一个名为 OnMessageReceived 的事件,该事件使用 MessageHandler 委托类型。

public event MessageHandler OnMessageReceived;
3. 定义事件触发方法

我们定义了一个 RaiseEvent 方法,用于触发事件。该方法检查是否有订阅者,并调用他们的处理方法。

protected virtual void RaiseEvent(string message)
{OnMessageReceived?.Invoke(message);
}
4. 触发事件时机

在适当的地方调用 RaiseEvent 方法来触发事件。SendMessage 方法用于发送消息,并在发送消息时调用 RaiseEvent 方法来触发事件。

public void SendMessage(string message)
{Console.WriteLine($"Publisher: Sending message '{message}'");RaiseEvent(message);
}
5. 定义处理事件方法

每个 Subscriber 实例都有一个 HandleMessage 方法,用于处理接收到的消息。

public void HandleMessage(string message)
{Console.WriteLine($"{_name} received message: {message}");
}
6. 订阅事件

Program 类的 Main 方法中,我们创建了 PublisherSubscriber 实例,并通过 += 运算符订阅事件。

var publisher = new Publisher();
var subscriber1 = new Subscriber("Subscriber 1");
var subscriber2 = new Subscriber("Subscriber 2");publisher.OnMessageReceived += subscriber1.HandleMessage;
publisher.OnMessageReceived += subscriber2.HandleMessage;
7. 发送消息并触发事件

我们调用 SendMessage 方法来发送消息,并触发事件。

publisher.SendMessage("Hello, World!");
publisher.SendMessage("Another message!");
8. 取消订阅事件(可选)

我们可以通过 -=运算符 取消订阅某个事件,以停止接收通知。

publisher.OnMessageReceived -= subscriber1.HandleMessage;

3) 核心步骤

// 1. 委托定义(签名规范)
public delegate void MessageHandler(string msg);// 2. 事件声明
public event MessageHandler OnMessageReceived;// 3. 事件触发方法
protected virtual void RaiseEvent(string message)
{OnMessageReceived?.Invoke(message);
}

4)简单示例

1. 定义事件
public class Button
{// 定义委托类型public delegate void ClickHandler(object sender, EventArgs e);// 定义事件public event ClickHandler Click;
}
2. 触发事件
public class Button
{public delegate void ClickHandler(object sender, EventArgs e);public event ClickHandler Click;public void OnClick(){// 触发事件Click?.Invoke(this, EventArgs.Empty);}
}
3. 订阅事件
public class Program
{public static void Main(){Button button = new Button();// 订阅事件button.Click += Button_Click;}private static void Button_Click(object sender, EventArgs e){Console.WriteLine("Button clicked!");}
}

2. EventHandler

在C#中,EventHandler 是一个预定义的委托类型,用于处理事件。它是 .NET Framework 中事件系统的一个重要组成部分,简化了事件的定义和使用过程

1)定义

EventHandler 是一个预定义的委托类型,它被设计用来处理不带任何额外数据的事件。其签名如下:

public delegate void EventHandler(object sender, EventArgs e);
  • object sender:表示触发事件的对象。
  • EventArgs e:包含事件数据的对象。对于 EventHandler,这个参数通常是 EventArgs.Empty,因为它不携带任何特定的数据。

2)用途

EventHandler 主要用于那些不需要传递额外信息的事件。例如,按钮点击事件通常只需要知道哪个控件触发了事件,而不需要其他详细信息。

3)使用步骤

1. 定义委托类型(省略)

从C# 2.0开始,可以直接使用预定义的 EventHandler 委托类型,无需手动定义。

2. 定义事件及触发事件的方法

接下来,在类中定义一个事件。使用 event 关键字来声明事件,并指定其对应的委托类型。

public class Publisher
{// 定义一个事件public event EventHandler MyEvent;// 触发事件的方法protected virtual void OnMyEvent(EventArgs e){MyEvent?.Invoke(this, e);}
}
3. 触发事件时机

在适当的地方触发事件,通常是当某个条件满足时。为了触发事件,我们调用 OnMyEvent 方法,并传入适当的参数。

public class Publisher
{public event EventHandler MyEvent;protected virtual void OnMyEvent(EventArgs e){MyEvent?.Invoke(this, e);}public void DoSomething(){Console.WriteLine("Doing something...");// 某些操作完成后触发事件OnMyEvent(EventArgs.Empty);}
}
4. 定义事件处理方法
public class Subscriber
{public void HandleEvent(object sender, EventArgs e){Console.WriteLine("Event received!");}
}
5. 订阅事件与触发事件

在需要接收事件通知的对象中订阅事件。这通常通过 += 运算符来完成。

public class Program
{public static void Main(string[] args){var publisher = new Publisher();var subscriber = new Subscriber();// 订阅事件publisher.MyEvent += subscriber.HandleEvent;// 触发事件publisher.DoSomething();}
}

4)完整代码示例

using System;public class Publisher
{// 定义 事件public event EventHandler MyEvent;// 定义 触发事件的方法protected virtual void OnMyEvent(EventArgs e){MyEvent?.Invoke(this, e);}// 触发事件public void DoSomething(){Console.WriteLine("Doing something...");// 某些操作完成后触发事件OnMyEvent(EventArgs.Empty);}
}public class Subscriber
{// 处理事件public void HandleEvent(object sender, EventArgs e){Console.WriteLine("Event received!");}
}public class Program
{public static void Main(string[] args){var publisher = new Publisher();var subscriber = new Subscriber();// 订阅事件publisher.MyEvent += subscriber.HandleEvent;// 触发事件publisher.DoSomething();}
}

运行结果:

Doing something...
Event received!

3. EventHandler<TEventArgs>

1)定义

EventHandler<TEventArgs> 是一个泛型委托类型,用于处理带有特定事件数据的事件。它允许你传递额外的信息给事件处理程序,从而增强事件机制的功能。它的签名如下:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs;
  • object sender:表示触发事件的对象。
  • TEventArgs e:包含事件数据的对象,必须继承自 EventArgs 类。允许你传递更多的信息给事件处理程序。

2)用途

EventHandler<TEventArgs> 主要用于那些需要传递额外信息的事件。通过使用泛型参数 TEventArgs,你可以指定一个具体的 EventArgs 子类来携带更多详细信息。

💡 提示:若无需自定义参数,可直接使用预定义的 EventHandler 委托。

3)使用步骤

假设我们正在开发一个温度传感器应用程序。当温度发生变化时,传感器会通知所有订阅者当前的温度值。

1. 定义自定义 EventArgs

首先,我们需要定义一个继承自 EventArgs 的类,用于携带温度变化的具体信息。

public class TemperatureChangedEventArgs : EventArgs
{public double NewTemperature { get; }public TemperatureChangedEventArgs(double newTemperature){NewTemperature = newTemperature;}
}
2. 定义发布者类(Publisher)

接下来,我们定义一个 TemperatureSensor 类,它负责发布温度变化事件。

public class TemperatureSensor
{// 使用 EventHandler<TEventArgs> 声明事件public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;private double _temperature;public double Temperature{get => _temperature;set{if (_temperature != value){_temperature = value;OnTemperatureChanged(new TemperatureChangedEventArgs(_temperature));}}}protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e){TemperatureChanged?.Invoke(this, e);}
}

在这个类中:

  • 我们声明了一个 TemperatureChanged 事件,使用了 EventHandler<TemperatureChangedEventArgs> 委托类型。
  • 在设置 Temperature 属性时,如果新的温度值与旧值不同,则触发 TemperatureChanged 事件。
3. 定义订阅者类(Subscriber)

现在,我们定义一个 Thermostat 类,它将订阅并处理温度变化事件。

public class Thermostat
{public void HandleTemperatureChange(object sender, TemperatureChangedEventArgs e){Console.WriteLine($"Temperature changed to {e.NewTemperature}°C");}
}

在这个类中,我们定义了一个 HandleTemperatureChange 方法,该方法将作为事件处理程序。

4. 主程序(订阅+触发事件)

最后,在主程序中,我们将创建 TemperatureSensorThermostat 实例,并订阅事件。

using System;public class Program
{static void Main(string[] args){var sensor = new TemperatureSensor();var thermostat = new Thermostat();// 订阅温度变化事件sensor.TemperatureChanged += thermostat.HandleTemperatureChange;// 改变温度值sensor.Temperature = 25.5;sensor.Temperature = 26.0;// 取消订阅事件(可选)sensor.TemperatureChanged -= thermostat.HandleTemperatureChange;// 再次改变温度值sensor.Temperature = 27.0;}
}

输出结果

运行上述代码后,控制台输出将如下所示:

Temperature changed to 25.5°C
Temperature changed to 26°C

4)完整代码示例

using System;
public class TemperatureChangedEventArgs : EventArgs
{public double NewTemperature { get; }public TemperatureChangedEventArgs(double newTemperature){NewTemperature = newTemperature;}
}
public class TemperatureSensor
{// 使用 EventHandler<TEventArgs> 声明事件public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;private double _temperature;public double Temperature{get => _temperature;set{if (_temperature != value){_temperature = value;OnTemperatureChanged(new TemperatureChangedEventArgs(_temperature));}}}protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e){TemperatureChanged?.Invoke(this, e);}
}public class Thermostat
{public void HandleTemperatureChange(object sender, TemperatureChangedEventArgs e){Console.WriteLine($"Temperature changed to {e.NewTemperature}°C");}
}
public class Program
{static void Main(string[] args){var sensor = new TemperatureSensor();var thermostat = new Thermostat();// 订阅温度变化事件sensor.TemperatureChanged += thermostat.HandleTemperatureChange;// 改变温度值sensor.Temperature = 25.5;sensor.Temperature = 26.0;// 取消订阅事件(可选)sensor.TemperatureChanged -= thermostat.HandleTemperatureChange;// 再次改变温度值sensor.Temperature = 27.0;}
}
  • EventHandler:是一个预定义的委托类型,适用于不需要传递额外信息的事件。其签名是 void EventHandler(object sender, EventArgs e)
  • EventHandler<TEventArgs>:是一个泛型委托类型,适用于需要传递额外信息的事件。它允许你指定一个派生自 EventArgs 的类型作为参数,从而传递更多数据。

4. 订阅事件的方式

1)订阅方式

  • 使用事件处理程序(处理事件的方法)订阅事件
  • 使用匿名方法订阅事件
  • 使用 Lambda 表达式订阅事件

2)订阅示例

using System;
public class DownloadCompletedEventArgs : EventArgs
{public string FileName { get; }public long FileSize { get; }public DownloadCompletedEventArgs(string name, long size){FileName = name;FileSize = size;}
}public class Downloader
{// 使用泛型EventHandlerpublic event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;protected virtual void OnDownloadCompleted(DownloadCompletedEventArgs e){DownloadCompleted?.Invoke(this, e);}public void StartDownload(DownloadCompletedEventArgs eventArgs){Console.WriteLine("StartDownload...");// 模拟下载Thread.Sleep(2000);OnDownloadCompleted(eventArgs);}
}class Logger
{public void LogDownload(object sender, DownloadCompletedEventArgs eventArgs){Console.WriteLine($"订阅方式-方法赋值:[{DateTime.Now}] {eventArgs.FileName}文件 {eventArgs.FileSize} 下载完成");}
}public class Program
{static void Main(string[] args){var downloader = new Downloader();var logger = new Logger();// 日志类 订阅下载完成事件// 订阅方式1:使用方法赋值downloader.DownloadCompleted += logger.LogDownload!;// 订阅方式2:使用匿名方法downloader.DownloadCompleted += delegate (object sender, DownloadCompletedEventArgs eventArgs){Console.WriteLine($"订阅方式-匿名方法:[{DateTime.Now}] {eventArgs.FileName}文件 {eventArgs.FileSize} 下载完成");}!;// 订阅方式3:使用 Lambda 表达式downloader.DownloadCompleted += (sender, eventArgs) =>{Console.WriteLine($"订阅方式 - Lambda 表达式:[{DateTime.Now}] {eventArgs.FileName}文件 {eventArgs.FileSize} 下载完成");}!;// 触发事件downloader.StartDownload(new DownloadCompletedEventArgs("file", 1224));}
}

运行结果:

StartDownload...
订阅方式-方法赋值:[2025/3/12 16:02:41] file文件 1224 下载完成
订阅方式-匿名方法:[2025/3/12 16:02:41] file文件 1224 下载完成
订阅方式 - Lambda 表达式:[2025/3/12 16:02:41] file文件 1224 下载完成

5. 事件访问器

事件访问器(Event Accessors)允许我们在订阅或取消订阅事件时执行自定义逻辑。通过重写 addremove 访问器,可以在事件订阅和取消订阅时进行额外的操作。

public class Publisher
{private EventHandler _myEvent;public event EventHandler MyEvent{add{Console.WriteLine($"添加订阅: {value.Method.Name}");_myEvent += value;}remove{Console.WriteLine($"移除订阅: {value.Method.Name}");_myEvent -= value;}}protected virtual void OnMyEvent(EventArgs e){_myEvent?.Invoke(this, e);}public void DoSomething(){Console.WriteLine("Doing something...");OnMyEvent(EventArgs.Empty);}
}public class Subscriber
{public void HandleEvent(object sender, EventArgs e){Console.WriteLine("Event received!");}
}public class Program
{public static void Main(string[] args){var publisher = new Publisher();var subscriber = new Subscriber();// 订阅事件publisher.MyEvent += subscriber.HandleEvent;// 触发事件publisher.DoSomething();// 取消订阅事件publisher.MyEvent -= subscriber.HandleEvent;}
}

运行结果:

添加订阅: HandleEvent
Doing something...
Event received!
移除订阅: HandleEvent

6. 静态事件

类级别的事件,由类本身触发和订阅:

public class Logger 
{  public static event EventHandler GlobalLogEvent;  public static void Log(string message) {  GlobalLogEvent?.Invoke(null, new LogEventArgs(message));  }  
}  
// 订阅静态事件  
Logger GlobalLogEvent += (s, e) =>  Console.WriteLine($"全局日志:{e.Message}");  

四、事件应用场景

1. 主要应用场景

  • 用户界面交互:处理用户的输入和操作,如点击按钮、选择菜单项等。
  • 状态变化通知:当某个对象的状态发生变化时,通知其他依赖的对象。
  • 异步编程:在异步操作完成时通知调用方。

2. 应用场景示例

示例1:WinForms/WPF中的事件

UI框架大量使用事件机制(如 Button.ClickTextBox.TextChanged),通过XAML或代码绑定处理程序,实现用户交互响应。

// C#代码响应事件  
private void Button_Click(object sender, RoutedEventArgs e) 
{  Console.WriteLine("点击事件触发!");  
}  

示例2:状态变化通知

当某个对象的状态发生变化时,可以通过事件通知其他依赖的对象。

public class TemperatureSensor
{public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;private double _temperature;public double Temperature{get => _temperature;set{if (_temperature != value){_temperature = value;OnTemperatureChanged(new TemperatureChangedEventArgs(_temperature));}}}protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e){TemperatureChanged?.Invoke(this, e);}
}public class TemperatureChangedEventArgs : EventArgs
{public double NewTemperature { get; }public TemperatureChangedEventArgs(double newTemperature){NewTemperature = newTemperature;}
}public class Thermostat
{public void HandleTemperatureChange(object sender, TemperatureChangedEventArgs e){Console.WriteLine($"Temperature changed to {e.NewTemperature}°C");}
}public class Program
{public static void Main(string[] args){var sensor = new TemperatureSensor();var thermostat = new Thermostat();// 订阅温度变化事件sensor.TemperatureChanged += thermostat.HandleTemperatureChange;// 改变温度值sensor.Temperature = 25.5;sensor.Temperature = 26.0;}
}

示例3: 异步编程

事件还可以用于异步操作完成时通知调用方。

using System;
using System.Threading.Tasks;public class TaskRunner
{public event EventHandler<TaskCompletedEventArgs> TaskCompleted;public async Task RunTaskAsync(){Console.WriteLine("Starting task...");await Task.Delay(2000); // 模拟耗时操作Console.WriteLine("Task completed.");OnTaskCompleted(new TaskCompletedEventArgs(true, "Task finished successfully."));}protected virtual void OnTaskCompleted(TaskCompletedEventArgs e){TaskCompleted?.Invoke(this, e);}
}public class TaskCompletedEventArgs : EventArgs
{public bool Success { get; }public string Message { get; }public TaskCompletedEventArgs(bool success, string message){Success = success;Message = message;}
}public class Program
{public static async Task Main(string[] args){var runner = new TaskRunner();// 订阅任务完成事件runner.TaskCompleted += (sender, e) =>{Console.WriteLine($"Task result: {e.Success}, Message: {e.Message}");};// 执行异步任务await runner.RunTaskAsync();}
}

运行结果:

Starting task...
Task completed.
Task result: True, Message: Task finished successfully.

示例4:设计模式实践

在MVVM架构中,事件常与命令模式结合使用:

public class RelayCommand : ICommand 
{public event EventHandler? CanExecuteChanged;public void RaiseCanExecuteChanged() {CanExecuteChanged?.Invoke(this, EventArgs.Empty);}
}

这种模式有效分离UI逻辑与业务逻辑。

五、使用须知

1)避免内存泄漏

事件订阅会导致订阅者对象无法被垃圾回收,除非显式取消订阅。因此,确保在不再需要监听事件时取消订阅。

事件处理程序会保持对订阅对象的引用,这可能会影响垃圾回收。应确保在不再需要事件处理程序时及时取消订阅。

public class Program
{public static void Main(string[] args){var publisher = new Publisher();var subscriber = new Subscriber();// 订阅事件publisher.MyEvent += subscriber.HandleEvent;// 触发事件publisher.DoSomething();// 取消订阅事件publisher.MyEvent -= subscriber.HandleEvent;}
}

确保在订阅者不再需要时取消订阅事件,防止对象被意外保留:

public class Subscriber 
{private EventPublisher _publisher;public Subscriber(EventPublisher publisher) {_publisher = publisher;_publisher.MyEvent += HandleEvent;}public void Dispose() {_publisher.MyEvent -= HandleEvent; // 取消订阅}
}

2)线程安全

在多线程环境中,事件的订阅和触发可能会引发线程安全问题。可以使用锁机制或其他同步手段来确保线程安全。

public class Publisher
{private EventHandler _myEvent;private readonly object _lockObject = new object();public event EventHandler MyEvent{add{lock (_lockObject){_myEvent += value;}}remove{lock (_lockObject){_myEvent -= value;}}}protected virtual void OnMyEvent(EventArgs e){lock (_lockObject){_myEvent?.Invoke(this, e);}}public void DoSomething(){Console.WriteLine("Doing something...");OnMyEvent(EventArgs.Empty);}
}
// 安全触发方式
var localCopy = TheEvent;
localCopy?.Invoke(this, args);

3)空事件检查

避免空引用异常,推荐使用空条件运算符(?.):

// 错误示例:未检查事件是否为空
if (MyEvent != null) MyEvent(this, EventArgs.Empty);// 优化写法(C# 6.0+)
MyEvent?.Invoke(this, EventArgs.Empty);

4)事件处理程序的性能

避免在频繁触发的事件中执行耗时操作,另外频繁地发布和订阅事件可能会影响性能,特别是在事件处理程序较多的情况下。应尽量减少不必要的事件发布和订阅。

5)性能优化

高频触发事件时,考虑使用 WeakEventManager 防止内存泄漏。

2. 事件的优势

1)解耦发布者和订阅者

事件允许发布者和订阅者之间松散耦合,提高代码的可维护性和可扩展性。

2)支持多个订阅者

一个事件可以有多个订阅者,每个订阅者都可以定义自己的处理逻辑。

3)灵活的通信机制

事件提供了一种灵活的通信机制,适用于各种场景,如 UI 交互、后台任务完成通知等。

3. 最佳实践

1)使用 EventHandler<TEventArgs> 处理自定义事件

对于自定义事件,建议使用 EventHandler<TEventArgs> 作为事件委托类型。这样可以使你的事件处理机制更加一致和易于理解。

避免重复定义委托类型,提升代码简洁性。

// 自定义事件参数
public class TemperatureChangedEventArgs : EventArgs 
{public int NewTemperature { get; set; }
}// 事件声明
public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;// 触发事件时传递数据
protected virtual void OnTemperatureChanged(int newTemp) 
{TemperatureChanged?.Invoke(this, new TemperatureChangedEventArgs { NewTemperature = newTemp });
}

2)遵循命名约定

  • 事件的命名应遵循 C# 的命名约定,如使用 “Event” 为后缀 或 TemperatureChangedTaskCompleted 等形式作为事件的名称。
  • OnRaise为前缀命名触发方法,确保参数合法性检查。
// 触发事件的方法
protected virtual void OnMyEvent(EventArgs e)
{MyEvent?.Invoke(this, e);
}

3)避免过度使用事件

虽然事件非常灵活,但在某些情况下,过度使用事件可能会导致代码难以阅读和维护。

4. 常见问题与解答

Q1. 事件能否被继承或重写?

事件可以被继承,但需通过 override 关键字重写事件的访问器:

public class BaseClass {public virtual event EventHandler MyEvent;
}public class DerivedClass : BaseClass 
{public override event EventHandler MyEvent {add { base.MyEvent += value; }remove { base.MyEvent -= value; }}
}

Q2. 如何实现事件的多播顺序控制?

事件的多播顺序由订阅顺序决定,无法直接修改。若需自定义顺序,需手动管理委托链:

// 手动管理委托链
private MyEventHandler _customHandlers;public event MyEventHandler CustomEvent 
{add { _customHandlers += value; }remove { _customHandlers -= value; }
}// 触发时按逆序执行(例如优先执行高优先级方法)
protected virtual void OnCustomEvent() 
{if (_customHandlers != null) {var handlers = _customHandlers.GetInvocationList().Reverse();foreach (Delegate handler in handlers) {((MyEventHandler)handler)(this, EventArgs.Empty);}}
}

4. 小结

C#事件是实现松耦合、响应式编程的核心机制,其核心在于委托的封装发布-订阅模式的灵活应用。通过事件,开发者可以:

  1. 解耦对象:发布者无需知道订阅者具体是谁。
  2. 动态响应:在事件发生时立即执行相关逻辑。
  3. 扩展性:轻松添加或移除事件处理程序。

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
MSDN文档 - 事件


http://www.ppmy.cn/server/175005.html

相关文章

HarmonyOS

概述 HarmonyOS与Android操作系统对比 1.内核方面的对比&#xff1a; 安卓系统是基于linux的宏内核设计。宏内核包含了操作系统绝大多数的功能和模块&#xff0c;而且这些功能和模块都具有最高的权限&#xff0c;只要一个模块出错&#xff0c;整个系统就会崩溃&#xff0c;这也…

每日十题八股-2025年3月13日-关于垃圾回收的笔记

被面试伤透了心&#xff0c;手撕不出来的算法题&#xff0c;背得零零落落的八股。最经常说的话是&#xff1a;对不起&#xff0c;我不太记得了&#xff0c;我不太清楚这个&#xff0c;我没有考虑到。然后一被问并发就回答&#xff1a;加锁&#xff0c;加锁加锁。 以下的各种图…

DeepSeek Kimi详细生成PPT的步骤

以下是使用 DeepSeek 和 Kimi 协作生成 PPT 的详细步骤&#xff0c;结合了两者的优势实现高效创作&#xff1a; 第一步&#xff1a;使用 DeepSeek 生成 PPT 大纲或内容 明确需求并输入提示词 在 DeepSeek 的对话界面中&#xff0c;输入具体指令&#xff0c;要求生成 PPT 大纲或…

javascript Day_1

script的书写位置 1&#xff0c;写在</body>标签的正上方 <body><script>const newItem 1</script> </body>2&#xff0c;写在外部&#xff0c;然后引入到HTML文件 <script src"script.js"></script> 输入输出 1.输…

vscode接入DeepSeek 免费送2000 万 Tokens 解决DeepSeek无法充值问题

1. 在vscode中安装插件 Cline 2.打开硅基流动官网 3. 注册并登陆&#xff0c;邀请码 WpcqcXMs 4.登录后新建秘钥 5. 在vscode中配置cline (1) API Provider 选择 OpenAI Compatible &#xff1b; (2) Base URL设置为 https://api.siliconflow.cn](https://api.siliconfl…

密码学 网络安全 科普 网络安全密码技术

网络加密包括密码技术和网络加密方法两个方面。 一、 密码技术   密码技术一般分为常规密码和公钥密码。   常规密码是指收信方和发信方使用相同的密钥&#xff0c;即加密密钥和解密密钥是相同或等价的。比较著名的常规密码算法有DES及其各种变形、IDEA、FEAL、Skipjack…

前端 Webpack 面试题

1、什么是 Webpack?它有什么作用? Webpack 是一个前端资源打包工具,用于将 JavaScript、CSS、图片等项目资源进行模块化管理和打包。它能够将复杂的项目结构转化为浏览器友好的代码,提高前端项目的开发效率和性能。 模块打包:Webpack 将项目中的各个模块及依赖打包成一个…

专业视角:set 和 multiset的原理与应用解析

文章目录 前言关联式容器键值对树形结构的关联式容器 set1. set 的定义注意以下八点&#xff1a;2. set 的常用操作3. set 的迭代器4. set 的底层实现5. 自定义排序&#xff08;比较器&#xff09;6. set 与 multiset 的区别7. 示例&#xff1a;set 的完整代码 multiset 前言 …