如何通过MEF容器在Command中正确调用ViewModel?
你遇到的核心问题是构造阶段的循环依赖:ViewModel构造时获取Command,Command构造时导入ViewModel,导致实例化时死循环。以下是几种可行的解决方式:
1. 使用MEF的延迟导入(Lazy)
MEF原生支持Lazy<T>类型的导入,它会延迟依赖对象的实例化,直到你第一次访问Value属性时才创建对象。这种方式能直接打破构造阶段的循环。
代码示例:
public class CommandXY : ICommand { private readonly Lazy<MainViewModel> _lazyMainVm; [ImportingConstructor] public CommandXY(Lazy<MainViewModel> lazyMainVm) { _lazyMainVm = lazyMainVm; } public void Execute(object parameter) { // 仅在需要使用时才获取ViewModel实例 var mainVm = _lazyMainVm.Value; mainVm.DoSomething(); } // 实现ICommand的其他成员(CanExecute、CanExecuteChanged等) }
ViewModel侧无需修改原有的获取逻辑:
public MainViewModel() { CommandXY = MefComponent.GetImplementationOf<CommandXY>(); }
此时Command构造时只会拿到Lazy<MainViewModel>的包装对象,不会立即实例化ViewModel;直到Command执行时,ViewModel已经完成构造,循环被打破。
2. 改用属性注入替代构造注入
不在Command的构造函数中依赖ViewModel,而是通过标记[Import]的属性让MEF在Command构造完成后再注入ViewModel,避免构造阶段的互相依赖。
代码示例:
public class CommandXY : ICommand { // 用属性注入替代构造注入 [Import] public MainViewModel MainViewModel { get; set; } public CommandXY() { // 构造函数不包含任何依赖ViewModel的逻辑 } public void Execute(object parameter) { if (MainViewModel != null) { MainViewModel.DoSomething(); } } // 实现ICommand的其他成员 }
注意:确保你的MainViewModel已经标记了[Export(typeof(MainViewModel))],MEF才能找到对应的实例注入到Command的属性中。
3. 通过CommandParameter传递ViewModel
在XAML绑定Command时,直接将ViewModel自身作为CommandParameter传递给Execute方法,Command无需依赖注入,直接从参数获取ViewModel实例。
XAML绑定示例:
<Button Content="执行命令" Command="{Binding CommandXY}" CommandParameter="{Binding}" />
Command代码示例:
public class CommandXY : ICommand { public void Execute(object parameter) { var mainVm = parameter as MainViewModel; if (mainVm != null) { mainVm.DoSomething(); } } // 实现ICommand的其他成员 }
这种方式完全消除了Command与ViewModel的依赖关系,耦合度最低,适合简单场景。
4. 使用回调/事件机制解耦
让Command不直接依赖ViewModel,而是通过回调委托或事件触发ViewModel的逻辑,由ViewModel在构造时绑定回调。
代码示例:
public class CommandXY : ICommand { // 定义回调委托 public Action<object> OnExecute { get; set; } public void Execute(object parameter) { OnExecute?.Invoke(parameter); } // 实现ICommand的其他成员 }
ViewModel侧绑定回调:
public MainViewModel() { CommandXY = MefComponent.GetImplementationOf<CommandXY>(); // 将ViewModel的方法作为回调传入Command CommandXY.OnExecute = (param) => this.DoSomething(param); }
这种方式彻底解耦了两者的依赖,Command无需知晓ViewModel的具体类型。
额外注意事项
- 所有需要MEF导出的类(
MainViewModel、CommandXY)都必须标记对应的[Export]特性,例如:[Export(typeof(MainViewModel))] public class MainViewModel { ... } [Export(typeof(CommandXY))] public class CommandXY : ICommand { ... } - 确保MEF容器初始化完成后再进行对象的获取与调用,避免未完成组合导致的注入失败。
内容的提问来源于stack exchange,提问作者Censored4452

