You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌套属性ClassC.Cost变更时,FullyObservableCollection的ItemPropertyChanged事件未触发导致父类计算属性无法自动更新的问题排查

嵌套属性ClassC.Cost变更时,FullyObservableCollection的ItemPropertyChanged事件未触发导致父类计算属性无法自动更新的问题排查

咱们先捋清楚问题出在哪,再一步步解决:

核心问题拆解

  1. 基础接口缺失:你的ClassA、ClassB、ClassC都没有实现INotifyPropertyChanged接口!FullyObservableCollection要求泛型参数T实现这个接口才能监听属性变化,更别说嵌套对象的变化了——连最基础的事件触发都做不到。
  2. 嵌套监听只做了一层:现有代码的SubscribeToNestedProperties只监听了直接嵌套的INotifyPropertyChanged对象,既没有递归处理更深层次的嵌套(比如ClassC的变化),也没有处理嵌套集合(比如ClassA里的BCollection)内部元素的变化。
  3. 嵌套事件未向上传递:当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 11:25:28