MVVM架构下如何安全同步两个ViewModel的Selected属性?
问题背景
有两个视图模型ViewModelA和ViewModelB,二者都是selected值的可信数据源,都包含可触发OnPropertyChanged事件的Selected属性。需求是:当其中一方的Selected值更新时,另一方能自动同步并收到变更通知,同时要彻底规避无限循环的风险。
当前采用的方案是让两个ViewModel互相持有引用,在属性变更时直接同步设置对方的Selected属性。但这种方式存在严重隐患:一旦新旧值的比较逻辑出错,就会触发无限循环(A设值→通知B→B设值→通知A→……),仅靠SetProperty的相同值不触发通知的逻辑来规避,可靠性极低。
可行解决方案
方案1:引入共享数据源(推荐)
将selected的核心数据抽离到一个独立的共享数据模型(比如SharedSelectionModel),让ViewModelA和ViewModelB都依赖这个共享模型,而非互相依赖。
- 共享模型实现
INotifyPropertyChanged,维护Selected属性:
public class SharedSelectionModel : INotifyPropertyChanged { private object _selected; public object Selected { get => _selected; set { if (_selected != value) { _selected = value; OnPropertyChanged(nameof(Selected)); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
- 两个ViewModel注入这个共享模型,监听其
PropertyChanged事件,同时自己的Selected属性直接代理共享模型的值:
public class ViewModelA : INotifyPropertyChanged { private readonly SharedSelectionModel _sharedModel; public ViewModelA(SharedSelectionModel sharedModel) { _sharedModel = sharedModel; _sharedModel.PropertyChanged += OnSharedModelPropertyChanged; } public object Selected { get => _sharedModel.Selected; set => _sharedModel.Selected = value; } private void OnSharedModelPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(SharedSelectionModel.Selected)) { OnPropertyChanged(nameof(Selected)); } } // INotifyPropertyChanged接口实现代码省略 }
ViewModelB的实现和ViewModelA完全一致。这种方式从根源上避免了循环依赖,所有变更都通过单一可信源传递,逻辑清晰且安全。
方案2:添加变更标记,阻断循环触发
如果无法抽离共享模型,可以在设置属性时添加一个isUpdating标记,标记当前是否是同步引发的属性变更,从而跳过反向同步逻辑。
以ViewModelA为例:
public class ViewModelA : INotifyPropertyChanged { private bool _isUpdating; private object _selected; public ViewModelB B { get; set; } public object Selected { get => _selected; set { if (_selected != value) { _selected = value; OnPropertyChanged(nameof(Selected)); // 仅当不是同步更新时,才通知对方 if (!_isUpdating) { _isUpdating = true; B.Selected = value; _isUpdating = false; } } } } // INotifyPropertyChanged接口实现代码省略 }
ViewModelB需要添加完全相同的_isUpdating标记逻辑。这种方式能直接阻断循环,但依赖ViewModel之间的引用,解耦程度不如共享数据源方案。
方案3:使用事件总线实现弱耦合通信
通过一个全局或局部的事件总线,让两个ViewModel订阅属性变更事件,当自身属性变更时发布事件,收到事件时更新自身属性(同时判断是否是自己发布的事件,避免循环)。
示例事件总线简化实现:
public static class SelectionEventBus { public static event Action<object> SelectionChanged; public static void PublishSelectionChanged(object newValue) { SelectionChanged?.Invoke(newValue); } }
ViewModelA的实现:
public class ViewModelA : INotifyPropertyChanged { private object _selected; public ViewModelA() { SelectionEventBus.SelectionChanged += OnSelectionChanged; } public object Selected { get => _selected; set { if (_selected != value) { _selected = value; OnPropertyChanged(nameof(Selected)); // 发布变更事件 SelectionEventBus.PublishSelectionChanged(value); } } } private void OnSelectionChanged(object newValue) { // 仅当新值和当前值不同时更新,避免自己发布的事件触发自身更新 if (_selected != newValue) { _selected = newValue; OnPropertyChanged(nameof(Selected)); } } // 注意:ViewModel销毁时需取消订阅,避免内存泄漏 }
ViewModelB的实现逻辑与ViewModelA一致。这种方式实现了ViewModel之间的完全解耦,但需要注意事件订阅的内存泄漏问题,在ViewModel生命周期结束时要及时取消订阅。
内容的提问来源于stack exchange,提问作者Olafvolafka

