嵌套属性ClassC.Cost变更时,FullyObservableCollection的ItemPropertyChanged事件未触发导致父类计算属性无法自动更新的问题排查
嵌套属性ClassC.Cost变更时,FullyObservableCollection的ItemPropertyChanged事件未触发导致父类计算属性无法自动更新的问题排查
咱们先捋清楚问题出在哪,再一步步解决:
核心问题拆解
- 基础接口缺失:你的
ClassA、ClassB、ClassC都没有实现INotifyPropertyChanged接口!FullyObservableCollection要求泛型参数T实现这个接口才能监听属性变化,更别说嵌套对象的变化了——连最基础的事件触发都做不到。 - 嵌套监听只做了一层:现有代码的
SubscribeToNestedProperties只监听了直接嵌套的INotifyPropertyChanged对象,既没有递归处理更深层次的嵌套(比如ClassC的变化),也没有处理嵌套集合(比如ClassA里的BCollection)内部元素的变化。 - 嵌套事件未向上传递:当
ClassC.Cost变化时,ClassB不会自动触发自己的PropertyChanged事件——因为C是引用类型,它的内部属性变化不会让父对象的属性变化事件触发,相当于底层的变化“断了层”,传不到上层的集合事件里。
具体解决方案
第一步:给所有嵌套类补全INotifyPropertyChanged实现
这是基础中的基础,没有这个,所有监听都是空谈:
public class ClassC : INotifyPropertyChanged { private int _cost; public int Cost { get => _cost; set { if (_cost != value) { _cost = value; OnPropertyChanged(nameof(Cost)); } } } private int _currency; public int Currency { get => _currency; set { if (_currency != value) { _currency = value; OnPropertyChanged(nameof(Currency)); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class ClassB : INotifyPropertyChanged { private ClassC _c; public ClassC C { get => _c; set { if (_c != value) { // 先取消旧对象的事件订阅 if (_c != null) _c.PropertyChanged -= C_PropertyChanged; _c = value; // 订阅新对象的事件 if (_c != null) _c.PropertyChanged += C_PropertyChanged; OnPropertyChanged(nameof(C)); } } } // 把ClassC的变化事件向上传递,上层能知道是"C.Cost"这类嵌套属性变了 private void C_PropertyChanged(object sender, PropertyChangedEventArgs e) { OnPropertyChanged($"C.{e.PropertyName}"); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class ClassA : INotifyPropertyChanged { private FullyObservableCollection<ClassB> _bCollection; public FullyObservableCollection<ClassB> BCollection { get => _bCollection; set { if (_bCollection != value) { // 取消旧集合的事件订阅 if (_bCollection != null) _bCollection.ItemPropertyChanged -= BCollection_ItemPropertyChanged; _bCollection = value; // 订阅新集合的事件 if (_bCollection != null) _bCollection.ItemPropertyChanged += BCollection_ItemPropertyChanged; OnPropertyChanged(nameof(BCollection)); OnPropertyChanged(nameof(SumOfBCost)); } } } public int SumOfBCost => BCollection?.Sum(b => b.C?.Cost ?? 0) ?? 0; // 集合内元素变化时,自动更新计算属性 private void BCollection_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e) { OnPropertyChanged(nameof(SumOfBCost)); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
第二步:修改FullyObservableCollection的嵌套监听逻辑
让它支持递归监听深层嵌套,以及嵌套集合的内部变化:
// 替换原有的SubscribeToNestedProperties方法 private void SubscribeToNestedProperties(object obj) { foreach (var property in obj.GetType().GetProperties()) { var propertyValue = property.GetValue(obj); if (propertyValue == null) continue; // 监听普通的INotifyPropertyChanged对象,递归处理它的嵌套属性 if (typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)) { var nestedNotify = propertyValue as INotifyPropertyChanged; nestedNotify.PropertyChanged += ChildPropertyChanged; SubscribeToNestedProperties(nestedNotify); } // 监听嵌套的FullyObservableCollection,订阅它的ItemPropertyChanged事件 else if (typeof(FullyObservableCollection<>).IsAssignableFrom(property.PropertyType.GetGenericTypeDefinition())) { var itemChangedEvent = property.PropertyType.GetEvent("ItemPropertyChanged"); if (itemChangedEvent != null) { EventHandler<ItemPropertyChangedEventArgs> handler = (s, e) => { var parentItem = obj as T; var index = Items.IndexOf(parentItem); if (index >= 0) { // 传递嵌套集合的变化,比如"BCollection[0].C.Cost" OnItemPropertyChanged(index, parentItem, new PropertyChangedEventArgs($"{property.Name}.{e.PropertyName}")); } }; itemChangedEvent.AddEventHandler(propertyValue, handler); } // 递归订阅集合内每个元素的嵌套属性 var itemsProp = property.PropertyType.GetProperty("Items"); if (itemsProp != null) { var items = itemsProp.GetValue(propertyValue) as IEnumerable; if (items != null) { foreach (var item in items) SubscribeToNestedProperties(item); } } } } } // 替换原有的UnsubscribeFromNestedProperties方法(逻辑对应订阅,避免内存泄漏) private void UnsubscribeFromNestedProperties(object obj) { foreach (var property in obj.GetType().GetProperties()) { var propertyValue = property.GetValue(obj); if (propertyValue == null) continue; if (typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)) { var nestedNotify = propertyValue as INotifyPropertyChanged; nestedNotify.PropertyChanged -= ChildPropertyChanged; UnsubscribeFromNestedProperties(nestedNotify); } else if (typeof(FullyObservableCollection<>).IsAssignableFrom(property.PropertyType.GetGenericTypeDefinition())) { var itemChangedEvent = property.PropertyType.GetEvent("ItemPropertyChanged"); if (itemChangedEvent != null) { // 注意:这里为了简化,handler和订阅时一致,实际项目建议用字典存储handler避免取消失败 EventHandler<ItemPropertyChangedEventArgs> handler = (s, e) => { var parentItem = obj as T; var index = Items.IndexOf(parentItem); if (index >= 0) { OnItemPropertyChanged(index, parentItem, new PropertyChangedEventArgs($"{property.Name}.{e.PropertyName}")); } }; itemChangedEvent.RemoveEventHandler(propertyValue, handler); } var itemsProp = property.PropertyType.GetProperty("Items"); if (itemsProp != null) { var items = itemsProp.GetValue(propertyValue) as IEnumerable; if (items != null) { foreach (var item in items) UnsubscribeFromNestedProperties(item); } } } } } // 替换原有的ChildPropertyChanged方法,找到嵌套对象的父元素并触发上层事件 private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) { // 找到当前集合中包含sender的父元素(比如ClassC的父是ClassB,而ClassB在集合里) var parentItem = Items.FirstOrDefault(item => item.GetType().GetProperties().Any(p => p.GetValue(item) == sender)); if (parentItem != null) { var index = Items.IndexOf(parentItem); if (index >= 0) { OnItemPropertyChanged(index, parentItem, e); } } }
测试验证
现在你可以写个简单的测试代码验证效果:
var classAList = new FullyObservableCollection<ClassA>(); var classA = new ClassA { BCollection = new FullyObservableCollection<ClassB> { new ClassB { C = new ClassC { Cost = 10 } } } }; classAList.Add(classA); // 订阅集合的ItemPropertyChanged事件 classAList.ItemPropertyChanged += (s, e) => { Console.WriteLine($"集合中索引{e.CollectionIndex}的元素,属性{e.PropertyName}发生变化"); Console.WriteLine($"当前SumOfBCost的值:{classA.SumOfBCost}"); }; // 修改ClassC的Cost,看事件是否触发 classA.BCollection[0].C.Cost = 20;
运行后应该能看到事件被触发,SumOfBCost自动更新为20。
备注:内容来源于stack exchange,提问作者user2953417
相关产品推荐
相关产品推荐

