如何减少MVVM模式中属性变更通知的冗余样板代码?
MVVM属性关联通知优化方案
1. 最优解:使用官方MVVM工具包的源生成器
你提到的[DependsOn]标记逻辑已经被微软官方的CommunityToolkit.Mvvm库原生实现,配合源生成器可以完全消除属性通知的模板代码,也不会出现漏写通知的问题。
改造后的代码示例:
// 给私有字段加[ObservableProperty]特性,工具会自动生成public的CustomEmail属性和对应的通知逻辑 [ObservableProperty] private string customEmail; // 标记计算属性依赖于CustomEmail,CustomEmail变更时会自动触发当前属性的变更通知 [DependsOn(nameof(CustomEmail))] public bool IsNotFaxEmail => !CustomEmail?.EndsWith("@fax.example.com") ?? false; [DependsOn(nameof(CustomEmail))] public bool IsSendAllowed => !string.IsNullOrWhiteSpace(CustomEmail) && IsNotFaxEmail;
这个方案不需要你手动写任何OnChanged调用,所有通知逻辑都会由源生成器在编译时自动生成,运行时没有反射开销,性能和手写代码一致。
2. 自研轻量实现(不引入第三方依赖)
如果不想引入外部库,你可以自己实现[DependsOn]特性和依赖通知逻辑,只需要修改ViewModel基类即可:
第一步:定义DependsOn特性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class DependsOnAttribute : Attribute { public string DependencyPropertyName { get; } public DependsOnAttribute(string dependencyPropertyName) { DependencyPropertyName = dependencyPropertyName; } }
第二步:在ViewModel基类中实现依赖缓存和自动通知
public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; // 缓存每个类型的属性依赖关系,避免每次触发通知都反射 private static readonly ConcurrentDictionary<Type, IReadOnlyDictionary<string, List<string>>> _dependencyCache = new(); protected ViewModelBase() { // 初始化当前类型的依赖缓存 var type = GetType(); _dependencyCache.GetOrAdd(type, t => { var dependencyMap = new Dictionary<string, List<string>>(); // 遍历所有属性,收集依赖关系 foreach (var property in t.GetProperties()) { var dependsOnAttrs = property.GetCustomAttributes<DependsOnAttribute>(); foreach (var attr in dependsOnAttrs) { if (!dependencyMap.TryGetValue(attr.DependencyPropertyName, out var relatedProps)) { relatedProps = new List<string>(); dependencyMap[attr.DependencyPropertyName] = relatedProps; } relatedProps.Add(property.Name); } } return dependencyMap; }); } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { // 触发当前属性的变更通知 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); // 触发所有关联属性的变更通知 if (_dependencyCache[GetType()].TryGetValue(propertyName, out var relatedProperties)) { foreach (var relatedProp in relatedProperties) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(relatedProp)); } } } }
第三步:业务属性写法
public string CustomEmail { get => customEmail; set { customEmail = value; // 只需要调用一次,所有关联属性的通知会自动触发 OnPropertyChanged(); } } [DependsOn(nameof(CustomEmail))] public bool IsNotFaxEmail => /* 你的计算逻辑 */; [DependsOn(nameof(CustomEmail))] public bool IsSendAllowed => /* 你的计算逻辑 */;
这个方案的优点是无外部依赖,兼容现有代码,只需要把原来多写的OnChanged调用去掉,给计算属性加标记即可。
3. 不推荐全用Converter的原因
Converter只适合处理通用、跨ViewModel的转换逻辑(比如字符串非空转Visibility),如果是业务相关的计算逻辑,全部用Converter会导致业务逻辑散落到多个Converter类中,代码维护成本远高于把计算逻辑放在ViewModel里,还会导致XAML绑定代码冗余,和你的诉求相悖。
基础优化补充
不管用哪种方案,都建议给OnPropertyChanged方法加上[CallerMemberName]特性,编译器会自动填入调用方的属性名,不需要手动写属性名字符串,避免拼写错误。
内容的提问来源于stack exchange,提问作者viraptor
相关产品推荐
相关产品推荐

