C# WPF依赖注入场景下类间通信避免循环引用的最优方案
符合OOP规则的通用解决方案:基于事件聚合器的解耦通信
该方案完全避开循环依赖问题,同时适配动态切换顶部视图的需求,严格遵循依赖倒置、单一职责、开闭原则等OOP核心规则。
核心逻辑
两个ViewModel均不直接依赖对方,仅依赖全局唯一的事件聚合器中介完成通信:
- 发送方仅需向事件聚合器发布指定类型的事件
- 接收方仅需向事件聚合器订阅自己关心的事件类型
- 双方完全感知不到对方的存在,耦合度降到最低
具体实现步骤
1. 定义事件聚合器抽象与实现
首先定义通用的事件发布订阅接口:
public interface IEventAggregator { void Subscribe<TEvent>(Action<TEvent> handler); void Publish<TEvent>(TEvent eventData); void Unsubscribe<TEvent>(Action<TEvent> handler); }
简易实现(可直接投入生产使用):
public class EventAggregator : IEventAggregator { private readonly ConcurrentDictionary<Type, List<object>> _handlers = new(); public void Subscribe<TEvent>(Action<TEvent> handler) { var eventType = typeof(TEvent); if (!_handlers.ContainsKey(eventType)) _handlers[eventType] = new List<object>(); _handlers[eventType].Add(handler); } public void Publish<TEvent>(TEvent eventData) { var eventType = typeof(TEvent); if (_handlers.TryGetValue(eventType, out var handlers)) { foreach (var handler in handlers.Cast<Action<TEvent>>()) handler(eventData); } } public void Unsubscribe<TEvent>(Action<TEvent> handler) { var eventType = typeof(TEvent); if (_handlers.TryGetValue(eventType, out var handlers)) handlers.Remove(handler); } }
2. DI容器注册
将事件聚合器注册为全局单例:
services.AddSingleton<IEventAggregator, EventAggregator>();
3. ViewModel通信实现
仅需在两个ViewModel的构造函数中注入IEventAggregator,无需依赖对方。
示例1:TopViewModel触发命令通知ContentViewModel
首先定义事件类(普通POCO即可):
public class SearchTriggeredEvent { public string Keyword { get; set; } }
TopViewModel发布事件:
public class TopViewModel { private readonly IEventAggregator _eventAggregator; public ICommand SearchCommand { get; } public TopViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; SearchCommand = new RelayCommand<string>(keyword => { _eventAggregator.Publish(new SearchTriggeredEvent { Keyword = keyword }); }); } }
ContentViewModel订阅事件:
public class ContentViewModel { private readonly IEventAggregator _eventAggregator; public ContentViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; // 订阅搜索事件 _eventAggregator.Subscribe<SearchTriggeredEvent>(OnSearchTriggered); } private void OnSearchTriggered(SearchTriggeredEvent e) { // 执行搜索逻辑 Console.WriteLine($"执行搜索:{e.Keyword}"); } }
示例2:ContentViewModel通知TopViewModel更新状态
仅需新增对应事件类,ContentViewModel发布、TopViewModel订阅即可,无需修改现有通信逻辑。
4. 适配动态切换顶部视图的需求
切换顶部视图时,只需在旧TopViewModel销毁前调用Unsubscribe方法取消所有事件订阅,新TopViewModel初始化时注册对应订阅即可,ContentViewModel无需做任何调整。
方案优势
- 完全消除循环依赖问题,两个ViewModel的初始化互不影响
- 符合OOP设计原则:双方仅依赖抽象的事件聚合器,不依赖具体实现;新增通信需求仅需新增事件类,无需修改现有VM代码
- 适配动态视图切换场景,扩展成本极低
- 可复用到其他跨ViewModel通信场景,比如主菜单ViewModel和内容ViewModel的通信
内容的提问来源于stack exchange,提问作者user2190492
相关产品推荐
相关产品推荐

