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

如何实现可感知依赖属性变更的只读计算属性?

解决多层依赖下计算属性重复计算的问题

这问题太典型了——每次调用TotalPoints都全量遍历计算,在多层依赖、数据量大的时候确实会拖慢性能。我给你几个实用的解决思路,从手动实现到框架辅助都有:

1. 手动实现:脏标记+观察者模式(最灵活)

核心思路是:只在依赖项变化时标记计算结果为“脏”,下次获取TotalPoints时再重新计算,平时直接返回缓存值。

步骤拆解:

  • 给Subject类添加属性变更事件,当Points修改时触发通知
  • Student类中维护一个脏标记(_totalPointsDirty),一旦收到Subject的变更通知,或者Subject集合有增减,就把标记设为true
  • TotalPoints的getter先检查脏标记:如果是true就重新计算并更新缓存,然后重置标记;否则直接返回缓存值

代码示例:

public class Subject
{
    private int _points;
    public int Points
    {
        get => _points;
        set
        {
            if (_points != value)
            {
                _points = value;
                // 触发变更通知
                PointsChanged?.Invoke();
            }
        }
    }

    // 定义变更事件
    public event Action PointsChanged;
}

public class Student
{
    private readonly List<Subject> _subjects = new List<Subject>();
    private int _totalPoints;
    private bool _totalPointsDirty = true;

    public IReadOnlyList<Subject> Subjects => _subjects.AsReadOnly();

    public int TotalPoints
    {
        get
        {
            if (_totalPointsDirty)
            {
                RecalculateTotalPoints();
                _totalPointsDirty = false;
            }
            return _totalPoints;
        }
    }

    // 添加科目时订阅变更事件
    public void AddSubject(Subject subject)
    {
        _subjects.Add(subject);
        subject.PointsChanged += OnSubjectPointsChanged;
        _totalPointsDirty = true;
    }

    // 移除科目时取消订阅
    public void RemoveSubject(Subject subject)
    {
        if (_subjects.Remove(subject))
        {
            subject.PointsChanged -= OnSubjectPointsChanged;
            _totalPointsDirty = true;
        }
    }

    // 收到科目分数变更时标记脏值
    private void OnSubjectPointsChanged()
    {
        _totalPointsDirty = true;
    }

    // 实际计算逻辑
    private void RecalculateTotalPoints()
    {
        _totalPoints = 0;
        foreach (var subject in _subjects)
        {
            _totalPoints += subject.Points;
        }
    }
}

2. 借助框架减少样板代码

如果不想手动写事件订阅和脏标记,可以用成熟的MVVM框架(比如CommunityToolkit.Mvvm),它们内置了依赖属性追踪和计算属性支持,能大大减少重复代码。

代码示例(用CommunityToolkit.Mvvm):

using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using System.Linq;

public partial class Subject : ObservableObject
{
    // 自动生成带变更通知的Points属性
    [ObservableProperty]
    private int points;
}

public partial class Student : ObservableObject
{
    private readonly ObservableCollection<Subject> _subjects = new ObservableCollection<Subject>();

    public ObservableCollection<Subject> Subjects => _subjects;

    // 计算属性,依赖Subjects中所有的Points
    public int TotalPoints => Subjects.Sum(s => s.Points);

    public Student()
    {
        // 监听科目集合变化
        _subjects.CollectionChanged += (s, e) =>
        {
            // 新加入的科目要监听其属性变化
            if (e.NewItems != null)
                foreach (Subject sub in e.NewItems)
                    sub.PropertyChanged += (_, __) => OnPropertyChanged(nameof(TotalPoints));
            
            // 移除的科目要取消监听
            if (e.OldItems != null)
                foreach (Subject sub in e.OldItems)
                    sub.PropertyChanged -= (_, __) => OnPropertyChanged(nameof(TotalPoints));
            
            // 集合变化时通知TotalPoints更新
            OnPropertyChanged(nameof(TotalPoints));
        };
    }
}

框架会帮你处理属性变更的通知逻辑,不用手动维护脏标记。

3. 即时更新(性能最优)

如果对性能要求极高,甚至不想等到获取TotalPoints时才计算,可以在依赖项变化时即时更新总分数,而不是延迟计算。

比如修改Subject的事件,传递旧值和新值,这样Student可以直接更新差值,不用全量遍历:

public class Subject
{
    private int _points;
    public int Points
    {
        get => _points;
        set
        {
            if (_points != value)
            {
                int oldValue = _points;
                _points = value;
                PointsChanged?.Invoke(oldValue, value);
            }
        }
    }

    // 传递旧值和新值的事件
    public event Action<int, int> PointsChanged;
}

// Student类中对应的处理逻辑
private void OnSubjectPointsChanged(int oldPoints, int newPoints)
{
    // 直接更新差值,不用重新遍历所有科目
    _totalPoints += newPoints - oldPoints;
}

这种方式适合科目数量多、分数频繁变化的场景,能把计算开销降到最低。


内容的提问来源于stack exchange,提问作者Siow Chee MIng

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 20:48:02