C# 事件与委托

一、委托基础

1. 委托定义

委托是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。

// 声明一个委托类型
public delegate void MyDelegate(string message);

// 使用委托
public class Program
{
    public static void Main()
    {
        // 创建委托实例并指向方法
        MyDelegate del = new MyDelegate(ShowMessage);
        
        // 调用委托
        del("Hello, Delegate!");
    }
    
    public static void ShowMessage(string msg)
    {
        Console.WriteLine(msg);
    }
}

2. 委托的多播

委托可以引用多个方法,形成多播委托。

public delegate void MultiCastDelegate(string message);

public class Program
{
    public static void Main()
    {
        MultiCastDelegate del = Method1;
        del += Method2; // 添加另一个方法
        del += Method3; // 可以继续添加
        
        del("Multicast message"); // 调用所有方法
        
        del -= Method2; // 移除一个方法
    }
    
    public static void Method1(string msg) => Console.WriteLine($"1: {msg}");
    public static void Method2(string msg) => Console.WriteLine($"2: {msg}");
    public static void Method3(string msg) => Console.WriteLine($"3: {msg}");
}

3. 泛型委托

C#提供了泛型委托类型,如FuncAction

// Action委托(无返回值)
Action<string> printAction = s => Console.WriteLine(s);
printAction("Hello Action!");

// Func委托(有返回值)
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5);
Console.WriteLine(result); // 输出8

常用内置泛型委托:

  • Action<T> - 表示无返回值的方法
  • Func<T, TResult> - 表示有返回值的方法
  • Predicate<T> - 表示返回bool的方法

二、事件基础

1. 事件定义

事件是基于委托的,它提供了一种更安全的方式来发布和订阅通知。

public class Button
{
    // 1. 定义委托类型
    // public delegate void ClickHandler(object sender, EventArgs e);
    
    // 2. 使用EventHandler标准模式(推荐)
    public event EventHandler Click;
    
    // 触发事件的方法
    public void OnClick()
    {
        Click?.Invoke(this, EventArgs.Empty);
    }
}

public class Program
{
    public static void Main()
    {
        var button = new Button();
        
        // 订阅事件
        button.Click += Button_Click;
        
        button.OnClick(); // 触发事件
    }
    
    private static void Button_Click(object sender, EventArgs e)
    {
        Console.WriteLine("Button clicked!");
    }
}

2. 自定义事件参数

public class CustomEventArgs : EventArgs
{
    public string Message { get; }
    
    public CustomEventArgs(string message)
    {
        Message = message;
    }
}

public class Button
{
    public event EventHandler<CustomEventArgs> Clicked;
    
    public void OnClicked(string message)
    {
        Clicked?.Invoke(this, new CustomEventArgs(message));
    }
}

// 使用
var button = new Button();
button.Clicked += (sender, e) => 
    Console.WriteLine($"Button clicked with message: {e.Message}");
button.OnClicked("Hello World!");

3. 事件访问器

可以自定义事件的add和remove访问器。

private EventHandler _clickHandlers;

public event EventHandler Click
{
    add
    {
        // 自定义添加逻辑
        _clickHandlers = (EventHandler)Delegate.Combine(_clickHandlers, value);
    }
    remove
    {
        // 自定义移除逻辑
        _clickHandlers = (EventHandler)Delegate.Remove(_clickHandlers, value);
    }
}

protected virtual void OnClick()
{
    _clickHandlers?.Invoke(this, EventArgs.Empty);
}

三、事件与委托的高级用法

1. 弱事件模式

解决内存泄漏问题,当订阅者被销毁时自动取消订阅。

// 弱事件管理器
public class WeakEventManager<TEventArgs> where TEventArgs : EventArgs
{
    private readonly Dictionary<string, WeakReference<EventHandler<TEventArgs>>> _handlers 
        = new Dictionary<string, WeakReference<EventHandler<TEventArgs>>>();
    
    private readonly object _lock = new object();
    
    public void AddHandler(EventHandler<TEventArgs> handler)
    {
        lock (_lock)
        {
            string key = Guid.NewGuid().ToString();
            _handlers[key] = new WeakReference<EventHandler<TEventArgs>>(handler);
        }
    }
    
    public void RemoveHandler(EventHandler<TEventArgs> handler)
    {
        lock (_lock)
        {
            foreach (var kvp in _handlers.ToArray())
            {
                if (kvp.Value.TryGetTarget(out var target) && target == handler)
                {
                    _handlers.Remove(kvp.Key);
                }
            }
        }
    }
    
    public void Raise(object sender, TEventArgs e)
    {
        lock (_lock)
        {
            foreach (var kvp in _handlers.ToArray())
            {
                if (kvp.Value.TryGetTarget(out var handler))
                {
                    handler(sender, e);
                }
                else
                {
                    _handlers.Remove(kvp.Key);
                }
            }
        }
    }
}

// 使用
public class Button
{
    private readonly WeakEventManager<EventArgs> _clickManager 
        = new WeakEventManager<EventArgs>();
    
    public event EventHandler Click
    {
        add => _clickManager.AddHandler(value);
        remove => _clickManager.RemoveHandler(value);
    }
    
    public void PerformClick()
    {
        _clickManager.Raise(this, EventArgs.Empty);
    }
}

2. 事件聚合器

集中管理多个事件。

public class EventAggregator
{
    private readonly Dictionary<Type, List<Delegate>> _handlers 
        = new Dictionary<Type, List<Delegate>>();
    
    public event EventHandler<MessageEventArgs> MessageReceived;
    
    public void Subscribe<TEvent>(Action<TEvent> handler)
    {
        var eventType = typeof(TEvent);
        if (!_handlers.ContainsKey(eventType))
        {
            _handlers[eventType] = new List<Delegate>();
        }
        
        _handlers[eventType].Add(handler);
    }
    
    public void Publish<TEvent>(TEvent eventToPublish)
    {
        var eventType = typeof(TEvent);
        if (_handlers.TryGetValue(eventType, out var handlers))
        {
            foreach (var handler in handlers.OfType<Action<TEvent>>())
            {
                handler(eventToPublish);
            }
        }
    }
}

// 使用
var aggregator = new EventAggregator();
aggregator.Subscribe<string>(msg => Console.WriteLine($"Received: {msg}"));
aggregator.Publish("Hello World!");

3. 异步事件处理

public class AsyncButton
{
    private readonly SynchronizationContext _context;
    public event EventHandler Clicked;
    
    public AsyncButton()
    {
        _context = SynchronizationContext.Current ?? 
                   new SynchronizationContext();
    }
    
    public async void OnClickedAsync()
    {
        // 模拟异步操作
        await Task.Delay(1000);
        
        // 在原始上下文中触发事件
        _context.Post(_ => 
        {
            Clicked?.Invoke(this, EventArgs.Empty);
        }, null);
    }
}

// 使用
var button = new AsyncButton();
button.Clicked += async (sender, e) => 
{
    Console.WriteLine("Start handling");
    await Task.Delay(500);
    Console.WriteLine("Finished handling");
};
button.OnClickedAsync();

四、事件与委托的最佳实践

1. 命名约定

  • 委托类型:以EventHandler结尾(如ClickEventHandler
  • 事件参数:继承自EventArgs
  • 事件名称:使用过去式(如Clicked而不是Clicking

2. 线程安全

private EventHandler _clickHandlers;
public event EventHandler Click
{
    add
    {
        lock (_lockObject)
        {
            _clickHandlers = (EventHandler)Delegate.Combine(_clickHandlers, value);
        }
    }
    remove
    {
        lock (_lockObject)
        {
            _clickHandlers = (EventHandler)Delegate.Remove(_clickHandlers, value);
        }
    }
}

3. 内存管理

// 使用弱引用防止内存泄漏
private WeakReference<EventHandler> _weakHandler;

public event EventHandler Click
{
    add
    {
        _weakHandler = new WeakReference<EventHandler>(value);
    }
    remove
    {
        // 实现弱引用移除逻辑
    }
}

4. 事件参数设计

public class DataChangedEventArgs : EventArgs
{
    public object OldValue { get; }
    public object NewValue { get; }
    
    public DataChangedEventArgs(object oldValue, object newValue)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}

// 使用
public event EventHandler<DataChangedEventArgs> DataChanged;

5. 虚事件模式

public class Control
{
    public event EventHandler Click;
    
    protected virtual void OnClick(EventArgs e)
    {
        Click?.Invoke(this, e);
    }
}

public class Button : Control
{
    protected override void OnClick(EventArgs e)
    {
        // 子类可以添加额外逻辑
        Console.WriteLine("Button click processing");
        
        base.OnClick(e); // 调用基类实现
    }
}

五、常见模式与技巧

1. 取消订阅模式

public class Subscriber : IDisposable
{
    private readonly Publisher _publisher;
    private EventHandler _handler;
    
    public Subscriber(Publisher publisher)
    {
        _publisher = publisher;
        _handler = OnEvent;
        _publisher.EventOccurred += _handler;
    }
    
    private void OnEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled");
    }
    
    public void Dispose()
    {
        _publisher.EventOccurred -= _handler;
    }
}

// 使用
using (var sub = new Subscriber(publisher))
{
    // 订阅期间处理事件
}
// 自动取消订阅

2. 事件总线模式

public static class EventBus
{
    private static readonly Dictionary<Type, List<Delegate>> _handlers 
        = new Dictionary<Type, List<Delegate>>();
    
    public static void Subscribe<TEvent>(Action<TEvent> handler)
    {
        var type = typeof(TEvent);
        if (!_handlers.ContainsKey(type))
        {
            _handlers[type] = new List<Delegate>();
        }
        
        _handlers[type].Add(handler);
    }
    
    public static void Publish<TEvent>(TEvent eventToPublish)
    {
        var type = typeof(TEvent);
        if (_handlers.TryGetValue(type, out var handlers))
        {
            foreach (var handler in handlers.OfType<Action<TEvent>>())
            {
                handler(eventToPublish);
            }
        }
    }
}

// 使用
EventBus.Subscribe<string>(msg => Console.WriteLine(msg));
EventBus.Publish("Hello EventBus!");

3. 事件过滤器

public class FilteredEventPublisher
{
    private readonly List<EventHandler<CustomEventArgs>> _handlers 
        = new List<EventHandler<CustomEventArgs>>();
    
    public event EventHandler<CustomEventArgs> Event
    {
        add => _handlers.Add(value);
        remove => _handlers.Remove(value);
    }
    
    public void RaiseEvent(CustomEventArgs e, Predicate<CustomEventArgs> filter)
    {
        foreach (var handler in _handlers)
        {
            if (filter(e))
            {
                handler(this, e);
            }
        }
    }
}

// 使用
var publisher = new FilteredEventPublisher();
publisher.Event += (s, e) => Console.WriteLine(e.Message);
publisher.RaiseEvent(new CustomEventArgs("Important"), e => e.IsImportant);

六、性能优化技巧

1. 减少事件处理开销

// 批量处理事件
private List<CustomEventArgs> _eventQueue = new List<CustomEventArgs>();
private readonly object _queueLock = new object();

public event EventHandler<CustomEventArgs> ProcessedEvent;

public void RaiseEvent(CustomEventArgs e)
{
    lock (_queueLock)
    {
        _eventQueue.Add(e);
    }
    
    Task.Run(() => ProcessQueue());
}

private void ProcessQueue()
{
    while (true)
    {
        List<CustomEventArgs> eventsToProcess;
        
        lock (_queueLock)
        {
            if (_eventQueue.Count == 0) break;
            eventsToProcess = _eventQueue.ToList();
            _eventQueue.Clear();
        }
        
        foreach (var e in eventsToProcess)
        {
            // 处理事件
        }
        
        ProcessedEvent?.Invoke(this, new CustomEventArgs("Batch processed"));
    }
}

2. 异步事件处理优化

public class AsyncEventPublisher
{
    private readonly SynchronizationContext _context;
    private event EventHandler<CustomEventArgs> _event;
    
    public AsyncEventPublisher()
    {
        _context = SynchronizationContext.Current ?? 
                   new SynchronizationContext();
    }
    
    public event EventHandler<CustomEventArgs> Event
    {
        add => _event += value;
        remove => _event -= value;
    }
    
    public void RaiseEventAsync(CustomEventArgs e)
    {
        Task.Run(() =>
        {
            // 模拟耗时操作
            Thread.Sleep(100);
            
            _context.Post(_ =>
            {
                _event?.Invoke(this, e);
            }, null);
        });
    }
}

3. 事件缓存

public class CachedEventPublisher
{
    private CustomEventArgs _cachedEvent;
    private DateTime _cacheTime;
    private readonly TimeSpan _cacheDuration = TimeSpan.FromSeconds(5);
    
    public event EventHandler<CustomEventArgs> Event;
    
    public void RaiseEvent(CustomEventArgs e)
    {
        _cachedEvent = e;
        _cacheTime = DateTime.Now;
        
        // 立即触发事件
        Event?.Invoke(this, e);
    }
    
    public CustomEventArgs GetCachedEvent()
    {
        if (_cachedEvent != null && 
            DateTime.Now - _cacheTime < _cacheDuration)
        {
            return _cachedEvent;
        }
        return null;
    }
}

七、调试与测试技巧

1. 事件调试

public class DebugEventPublisher
{
    public event EventHandler DebugEvent;
    
    public void RaiseDebugEvent()
    {
        Debug.WriteLine("DebugEvent is about to be raised");
        DebugEvent?.Invoke(this, EventArgs.Empty);
        Debug.WriteLine("DebugEvent was raised");
    }
}

// 使用
var publisher = new DebugEventPublisher();
publisher.DebugEvent += (s, e) => Debug.WriteLine("DebugEvent handled");
publisher.RaiseDebugEvent();

2. 单元测试事件

[TestClass]
public class EventTests
{
    [TestMethod]
    public void TestEventRaised()
    {
        // Arrange
        var publisher = new EventPublisher();
        bool eventRaised = false;
        
        publisher.Event += (s, e) => eventRaised = true;
        
        // Act
        publisher.RaiseEvent();
        
        // Assert
        Assert.IsTrue(eventRaised);
    }
    
    [TestMethod]
    public void TestEventArgs()
    {
        // Arrange
        var expected = new CustomEventArgs("Test");
        var actual = default(CustomEventArgs);
        var publisher = new EventPublisher();
        
        publisher.Event += (s, e) => actual = e;
        
        // Act
        publisher.RaiseEvent(expected);
        
        // Assert
        Assert.AreEqual(expected.Message, actual.Message);
    }
}

八、常见陷阱与解决方案

1. 内存泄漏

​问题​​:订阅者未取消订阅导致对象无法被GC回收。

​解决方案​​:

public class Subscriber : IDisposable
{
    private readonly Publisher _publisher;
    private EventHandler _handler;
    
    public Subscriber(Publisher publisher)
    {
        _publisher = publisher;
        _handler = OnEvent;
        _publisher.Event += _handler;
    }
    
    private void OnEvent(object sender, EventArgs e)
    {
        // 处理事件
    }
    
    public void Dispose()
    {
        _publisher.Event -= _handler;
    }
}

// 使用
using (var sub = new Subscriber(publisher))
{
    // 订阅期间处理事件
}
// 自动取消订阅

2. 竞态条件

​问题​​:多线程环境下事件订阅/取消订阅可能导致异常。

​解决方案​​:

private readonly object _lock = new object();
private EventHandler _eventHandlers;

public event EventHandler Event
{
    add
    {
        lock (_lock)
        {
            _eventHandlers = (EventHandler)Delegate.Combine(_eventHandlers, value);
        }
    }
    remove
    {
        lock (_lock)
        {
            _eventHandlers = (EventHandler)Delegate.Remove(_eventHandlers, value);
        }
    }
}

3. 事件参数不一致

​问题​​:不同订阅者期望不同的事件参数。

​解决方案​​:

public class EventPublisher
{
    // 基础事件
    public event EventHandler<BaseEventArgs> BaseEvent;
    
    // 特定事件
    public event EventHandler<SpecificEventArgs> SpecificEvent;
    
    public void RaiseEvents()
    {
        BaseEvent?.Invoke(this, new BaseEventArgs());
        SpecificEvent?.Invoke(this, new SpecificEventArgs());
    }
}

// 使用
publisher.BaseEvent += HandleBaseEvent;
publisher.SpecificEvent += HandleSpecificEvent;

九、高级模式

1. 事件聚合器模式

public class EventAggregator
{
    private readonly Dictionary<Type, List<Delegate>> _handlers 
        = new Dictionary<Type, List<Delegate>>();
    
    public void Subscribe<TEvent>(Action<TEvent> handler)
    {
        var type = typeof(TEvent);
        if (!_handlers.ContainsKey(type))
        {
            _handlers[type] = new List<Delegate>();
        }
        
        _handlers[type].Add(handler);
    }
    
    public void Publish<TEvent>(TEvent eventToPublish)
    {
        var type = typeof(TEvent);
        if (_handlers.TryGetValue(type, out var handlers))
        {
            foreach (var handler in handlers.OfType<Action<TEvent>>())
            {
                handler(eventToPublish);
            }
        }
    }
}

// 使用
var aggregator = new EventAggregator();
aggregator.Subscribe<string>(msg => Console.WriteLine(msg));
aggregator.Publish("Hello Aggregator!");

2. 弱事件模式

public class WeakEventManager<TEventArgs> where TEventArgs : EventArgs
{
    private readonly Dictionary<string, WeakReference<EventHandler<TEventArgs>>> _handlers 
        = new Dictionary<string, WeakReference<EventHandler<TEventArgs>>>();
    
    public void AddHandler(EventHandler<TEventArgs> handler)
    {
        var key = Guid.NewGuid().ToString();
        _handlers[key] = new WeakReference<EventHandler<TEventArgs>>(handler);
    }
    
    public void RemoveHandler(EventHandler<TEventArgs> handler)
    {
        foreach (var kvp in _handlers.ToArray())
        {
            if (kvp.Value.TryGetTarget(out var target) && target == handler)
            {
                _handlers.Remove(kvp.Key);
            }
        }
    }
    
    public void Raise(object sender, TEventArgs e)
    {
        foreach (var kvp in _handlers.ToArray())
        {
            if (kvp.Value.TryGetTarget(out var handler))
            {
                handler(sender, e);
            }
            else
            {
                _handlers.Remove(kvp.Key);
            }
        }
    }
}

3. 事件总线模式

public static class EventBus
{
    private static readonly Dictionary<Type, List<Delegate>> _handlers 
        = new Dictionary<Type, List<Delegate>>();
    
    public static void Subscribe<TEvent>(Action<TEvent> handler)
    {
        var type = typeof(TEvent);
        if (!_handlers.ContainsKey(type))
        {
            _handlers[type] = new List<Delegate>();
        }
        
        _handlers[type].Add(handler);
    }
    
    public static void Publish<TEvent>(TEvent eventToPublish)
    {
        var type = typeof(TEvent);
        if (_handlers.TryGetValue(type, out var handlers))
        {
            foreach (var handler in handlers.OfType<Action<TEvent>>())
            {
                handler(eventToPublish);
            }
        }
    }
}

// 使用
EventBus.Subscribe<string>(msg => Console.WriteLine(msg));
EventBus.Publish("Hello EventBus!");

十、总结

  1. ​委托​​是C#中实现回调机制的基础,EventHandlerFunc/Action是最常用的内置委托类型
  2. ​事件​​是基于委托的安全发布-订阅机制,遵循标准的add/remove访问器模式
  3. ​最佳实践​​包括使用标准事件模式、线程安全实现、弱引用防止内存泄漏等
  4. ​高级模式​​如事件聚合器、事件总线、弱事件等可以解决复杂场景下的通信问题
  5. ​调试技巧​​包括使用条件断点、日志记录和单元测试验证事件行为
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

code_shenbing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值