如何修复WPF项目中依赖注入的循环依赖异常
问题根源
你的循环依赖来自:NavService 构造函数依赖 HomeViewModel(及其他ViewModel),同时 HomeViewModel 构造函数又依赖 NavService。DI容器在创建这两个单例服务时,会陷入"创建NavService需要HomeViewModel,创建HomeViewModel又需要NavService"的死循环,最终抛出Circular Dependency Exception。
解决方案
方案1:重构NavService,通过IServiceProvider延迟解析ViewModel
把NavService中直接注入ViewModel的逻辑改成注入IServiceProvider,在需要导航时再动态解析对应的ViewModel,避免构造时的直接依赖。
示例代码:
// 重构后的NavService public class NavService : INotifyPropertyChanged { private readonly IServiceProvider _serviceProvider; private ViewModelBase _currentViewModel; public ViewModelBase CurrentViewModel { get => _currentViewModel; set { _currentViewModel = value; OnPropertyChanged(); } } public NavService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; // 初始化默认视图 CurrentViewModel = _serviceProvider.GetRequiredService<HomeViewModel>(); } // 通用导航方法 public void NavigateTo<TViewModel>() where TViewModel : ViewModelBase { CurrentViewModel = _serviceProvider.GetRequiredService<TViewModel>(); } // INotifyPropertyChanged实现 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
服务注册代码:
// App.xaml.cs中配置服务容器 services.AddSingleton<NavService>(); services.AddSingleton<HomeViewModel>(); services.AddSingleton<SettingsViewModel>(); services.AddSingleton<MainViewModel>();
此时HomeViewModel可安全构造注入NavService,因为NavService不再在构造时直接依赖HomeViewModel,而是通过服务提供者延迟获取实例。
方案2:使用属性注入(适合非核心依赖场景)
将NavService通过属性注入到HomeViewModel中,替代构造注入。注意:Microsoft.Extensions.DependencyInjection默认不自动支持属性注入,需手动处理依赖赋值。
示例代码:
// HomeViewModel public class HomeViewModel : ViewModelBase { public NavService NavService { get; set; } public ICommand NavigateToSettingsCommand => new RelayCommand(() => { NavService.NavigateTo<SettingsViewModel>(); }); }
服务注册时手动赋值属性:
services.AddSingleton<HomeViewModel>(sp => { var vm = new HomeViewModel(); vm.NavService = sp.GetRequiredService<NavService>(); return vm; }); services.AddSingleton<NavService>(sp => { var navService = new NavService(); navService.CurrentViewModel = sp.GetRequiredService<HomeViewModel>(); return navService; });
属性注入的缺点是依赖关系不够直观,仅建议在特殊场景下使用,优先选择方案1。
方案3:引入导航抽象接口,遵循依赖倒置原则
定义INavService抽象接口,让ViewModel依赖接口而非具体实现,同时让NavService通过服务提供者动态获取ViewModel实例,彻底解耦依赖。
示例代码:
// 导航服务接口 public interface INavService : INotifyPropertyChanged { ViewModelBase CurrentViewModel { get; } void NavigateTo<TViewModel>() where TViewModel : ViewModelBase; } // 具体实现 public class NavService : INavService { private readonly IServiceProvider _serviceProvider; private ViewModelBase _currentViewModel; public ViewModelBase CurrentViewModel => _currentViewModel; public NavService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _currentViewModel = _serviceProvider.GetRequiredService<HomeViewModel>(); } public void NavigateTo<TViewModel>() where TViewModel : ViewModelBase { _currentViewModel = _serviceProvider.GetRequiredService<TViewModel>(); OnPropertyChanged(nameof(CurrentViewModel)); } // INotifyPropertyChanged实现 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
HomeViewModel依赖抽象接口:
public class HomeViewModel : ViewModelBase { private readonly INavService _navService; public HomeViewModel(INavService navService) { _navService = navService; } public ICommand GoToSettingsCommand => new RelayCommand(() => { _navService.NavigateTo<SettingsViewModel>(); }); }
服务注册代码:
services.AddSingleton<INavService, NavService>(); services.AddSingleton<HomeViewModel>(); services.AddSingleton<SettingsViewModel>();
该方案既解决了循环依赖,又符合面向抽象编程的设计原则,是长期维护的最优选择。
总结
优先选择方案1或方案3,通过延迟解析ViewModel打破循环依赖,同时保持依赖关系的清晰性。避免在服务构造函数中注入会反向依赖自身的实例,尽量通过服务提供者或工厂模式动态获取依赖。
内容的提问来源于stack exchange,提问作者Jess Chan

