当前位置: 首页 > news >正文

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(){// Arrangevar publisher = new EventPublisher();bool eventRaised = false;publisher.Event += (s, e) => eventRaised = true;// Actpublisher.RaiseEvent();// AssertAssert.IsTrue(eventRaised);}[TestMethod]public void TestEventArgs(){// Arrangevar expected = new CustomEventArgs("Test");var actual = default(CustomEventArgs);var publisher = new EventPublisher();publisher.Event += (s, e) => actual = e;// Actpublisher.RaiseEvent(expected);// AssertAssert.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. ​调试技巧​​包括使用条件断点、日志记录和单元测试验证事件行为

相关文章:

  • DIT(Diffusion In Transformer)学习笔记
  • PID控制中,一阶低通滤波算法
  • c#TCPsever
  • 配置 Odoo 的 PostgreSQL 数据库以允许远程访问的步骤
  • 高级java每日一道面试题-2025年4月30日-基础篇[反射篇]-在反射中,`setAccessible(true)`的作用是什么?
  • LVGL -按键介绍 上
  • Spring AI如何调用本地部署的大模型
  • Learning vtkjs之ImplicitBoolean
  • 脏读、不可重复读、幻读示例
  • Clang-Tidy协助C++编译期检查
  • 在Windows系统上如何用Manifest管理嵌入式项目
  • 《Python实战进阶》No45:性能分析工具 cProfile 与 line_profiler
  • 架构进阶:72页集管IT基础设施蓝图设计方案【附全文阅读】
  • 软考中级-软件设计师 数据库(手写笔记)
  • 算法-冒泡排序
  • Ecology中拦截jquery.ajax请求接口后的数据
  • 【免费数据】2000-2020年中国4km分辨率逐日气象栅格数据(含9个气象变量)
  • windows11 编译 protobuf-3.21.12 c++
  • 大连理工大学选修课——机器学习笔记(4):NBM的原理及应用
  • 机器学习|通过线性回归了解算法流程
  • 新能源车盈利拐点:8家上市车企去年合计净利854亿元,多家扭亏
  • 近七成科创板公司2024年营收增长,285家营收创历史新高
  • 全国台联原会长杨国庆逝世,享年89岁
  • 白玉兰奖征片综述丨国产剧集创作的此消彼长
  • 上海国际咖啡文化节开幕,北外滩集结了超350个展位
  • 孕妇乘坐高铁突发临产,广西铁路部门协助送医平安产子