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#提供了泛型委托类型,如Func
和Action
。
// 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!");
十、总结
- 委托是C#中实现回调机制的基础,
EventHandler
和Func
/Action
是最常用的内置委托类型 - 事件是基于委托的安全发布-订阅机制,遵循标准的
add
/remove
访问器模式 - 最佳实践包括使用标准事件模式、线程安全实现、弱引用防止内存泄漏等
- 高级模式如事件聚合器、事件总线、弱事件等可以解决复杂场景下的通信问题
- 调试技巧包括使用条件断点、日志记录和单元测试验证事件行为