C# 事件机制详解与应用

78

事件是类或对象用来通知其他类或对象发生了某些事情的一种机制,基于委托。

事件本质上是委托的一种封装,提供更安全的订阅和取消订阅机制。

事件的使用步骤分为:声明委托、定义事件、触发事件和订阅事件。

要注意事件声明的标准模式,比如使用EventHandler委托或者自定义的委托类型,以及事件参数应该继承EventArgs。

高级部分包括事件访问器、弱事件模式、异步事件处理等。

弱事件模式是为了防止内存泄漏,特别是在长时间运行的应用中,比如WPF中的情况。

异步事件处理涉及到async和await,需要注意线程安全,比如在UI线程中更新界面需要使用Invoke。

了解常见问题,比如事件与委托的区别,如何安全触发事件,以及事件命名规范。

事件是什么

在 C# 中,事件(Event)是基于委托(Delegate)的机制,用于实现发布-订阅模式。

事件核心概念

定义:事件是类或对象用于通知其他类或对象发生了特定行为的机制

作用:解耦事件发布者(Publisher)和订阅者(Subscriber)

核心组成:

  • 委托类型(定义事件签名)

  • event 关键字

  • 事件触发方法

  • 事件订阅者(+=)和取消订阅(-=)

事件使用步骤

步骤1. 声明委托(定义事件签名)

public delegate void MyEventHandler(object sender, EventArgs e);

步骤2. 定义事件

public class Publisher
{
    // 使用 event 关键字声明事件
    public event MyEventHandler MyEvent;
}

步骤3. 触发事件

protected virtual void OnMyEvent()
{
    MyEvent?.Invoke(this, EventArgs.Empty); // 安全触发
}

步骤4. 订阅事件

public class Subscriber
{
    public void Subscribe(Publisher publisher)
    {
        publisher.MyEvent += HandleEvent;
    }

    private void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("事件被触发!");
    }
}

两种事件声明方式

方式1:基于自定义委托

public class Button
{
    public delegate void ClickHandler(object sender, EventArgs e);
    public event ClickHandler Clicked;
}

方式2:基于泛型 EventHandler<T>

(推荐 .NET 2.0+ 使用)

public class Button
{
    public event EventHandler<ClickEventArgs> Clicked;
}

// 自定义事件参数
public class ClickEventArgs : EventArgs
{
    public int ClickCount { get; set; }
}

最佳实践:事件参数类应继承自 EventArgs

实际应用场景示例

场景:温度监控报警

public class TemperatureMonitor
{
    public event EventHandler<TemperatureEventArgs> Overheated;

    public void CheckTemperature(float temperature)
    {
        if (temperature > 100)
        {
            OnOverheated(new TemperatureEventArgs(temperature));
        }
    }

    protected virtual void OnOverheated(TemperatureEventArgs e)
    {
        Overheated?.Invoke(this, e);
    }
}

public class TemperatureEventArgs : EventArgs
{
    public float Temperature { get; }

    public TemperatureEventArgs(float temperature)
    {
        Temperature = temperature;
    }
}

// 订阅方
public class Alarm
{
    public Alarm(TemperatureMonitor monitor)
    {
        monitor.Overheated += OnOverheated;
    }

    private void OnOverheated(object sender, TemperatureEventArgs e)
    {
        Console.WriteLine($"警报!当前温度:{e.Temperature}℃");
    }
}

高级用法

事件访问器(自定义 add/remove)

private EventHandler _myEvent;
public event EventHandler MyEvent
{
    add
    {
        Console.WriteLine("添加订阅");
        _myEvent += value;
    }
    remove
    {
        Console.WriteLine("移除订阅");
        _myEvent -= value;
    }
}

2. 弱事件模式(避免内存泄漏)

使用 WeakEventManager(WPF 等 UI 框架常用)

WeakEventManager<Publisher, EventArgs>
    .AddHandler(publisher, nameof(Publisher.MyEvent), HandleEvent);

3.异步事件处理

public event EventHandler<MyEventArgs> MyEventAsync;

private async void OnMyEventAsync()
{
    var handlers = MyEventAsync?.GetInvocationList();
    if (handlers != null)
    {
        foreach (var handler in handlers.Cast<EventHandler<MyEventArgs>>())
        {
            await Task.Run(() => handler(this, new MyEventArgs()));
        }
    }
}

常见问题解答

事件与委托的区别?

  • 事件是委托的封装,提供更安全的订阅机制

  • 外部只能 +=/-=,不能直接 = 或 Invoke

如何安全触发事件?

MyEvent?.Invoke(this, EventArgs.Empty);
//使用空值检查避免 NullReferenceException

总结

通过事件机制可以实现:

  • 松耦合的系统架构

  • 可扩展的通知系统

  • 标准化的回调处理

关键点:

  • 始终使用 EventHandler<T> 模式

  • 事件参数继承 EventArgs

  • 使用安全触发方式(?.Invoke)

  • 注意事件订阅导致的内存泄漏

完整的事件生命周期:

声明 → 触发 → 订阅 → 处理 → 取消订阅