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

C#关联类中如何正确处理PropertyChanged事件

C# 嵌套模型变更通知与缓存重算实现问题

我正在将一款遗留应用从C语言迁移至C#(.NET Core 5),落地更现代的软件开发实践。原C应用几乎将所有数据存储在内存的结构体中,且全部为全局可访问状态。这种模式对简单应用十分友好,但严重违反封装原则。面向对象实现中最接近该模式的方案是将数据存储设为static,出于可维护性考虑,我不打算采用这种方案。

当前应用专业性极强,涉及大量数学计算。旧版本会按需计算大量派生值(有时会重复计算),无法利用多线程与缓存能力提升性能。

为了用简单的类比示例说明问题,假设我们需要开发一个程序管理棒球队的相关成本:一支球队有固定运营成本,配备1名经理(有薪资),以及0到多名球员(每名球员也有对应薪资)。

初始数据模型设计

以下是我重新设计的数据模型:

public class Team {
    public int Costs { get; set; }
    public Manager Manager { get; set; }
    public HashSet<Player> Players { get; set; }

    private int _operatingCosts;
    public int OperatingCosts {
        get {
            return this._operatingCosts;
        }
        private set {
            // 注意这里是私有setter -- 仅允许在类内部基于自身值重算时赋值
            this._operatingCosts = value;
        }
    }
}
public class Manager {
   public string Name { get; set; }
   public int Salary { get; set; }
}
public class Player {
    public string Name { get; set; }
    public int Salary { get; set; }
}

原C代码会通过函数遍历所有数组、找到对应元素后求和完成计算。在重写的C#代码中,大部分这类计算逻辑十分简单,最适合实现为只读计算属性。

public class Manager {
    public int MonthlySalary {
        get {
            return this.Salary / 12;
        }
    }
}

基于INotifyPropertyChanged的缓存方案

难点出现在球队运营总成本的计算场景。这类计算可能逻辑复杂,我不希望每次访问都执行运算,因此需要缓存计算值,仅在事件触发时重新计算。我们明确知晓哪些变量变更会导致结果变化,因此可以在依赖属性的setter中触发事件。

public class Team : INotifyPropertyChanged {
    private int _costs;
    public int Costs {
        get {
            return this._costs;
        }
        set {
            this._costs = value;
            NotifyPropertyChanged();    // 成本变更时触发重算
        }
    }
    public int Name {
        get; set;    // 不需要实现INotifyPropertyChanged;缓存值不依赖该属性
    }
    private int _totalOperatingCost;
    public int TotalOperatingCost {
        get {
             return this._totalOperatingCost;
        }
        private set {
             this._totalOperatingCost = value;
        }
    }
    protected void RecalculateCosts() {
        this._totalOperatingCost = this.Team.Cost + this.Manager.Salary + this.Players.Sum(p => p.Salary);
    }
}

基于INotifyPropertyChanged的实现目前运行良好:我在会触发重算的字段上触发PropertyChanged事件,重算逻辑统一收敛在一处,没有问题。

核心卡点:子对象变更无法通知父级

现在问题出现了:如果经理的薪资发生变更要如何处理?

public class Manager : INotifyPropertyChanged {
    private int _salary;
    public int Salary {
        get {
            return this._salary;
        }
        set {  
            this._salary = value;
            NotifyParentPropertyChanged();
        }
    }
}

Manager类并不持有其所属Team的引用。当父类已经持有子类引用时,在子类中保留指向父类的反向引用很容易引发各类错误。如果我在此处调用NotifyPropertyChanged(),Manager只会更新自身的计算逻辑,完全不知道需要通知父类更新。这个问题理论上可以通过事件解决,但存在一个明显缺陷:

public class Team {
    public string Name { ... }
    private Manager _manager;
    public Manager Manager {
        get {
            return this._manager;
        }
        set {
            if (this._manager is not null) {
                this._manager.PropertyChanged -= ProcessParentPropertyChangedEvent;
            }

            this._manager = value;
            NotifyPropertyChanged();

            if (this._manager is not null) {
                this._manager.PropertyChanged += ProcessParentPropertyChangedEvent;
            }
        }
    }

    public Team() {
        if (this.Manager is not null) {
            this.Manager.PropertyChanged += ProcessParentPropertyChangedEvent;
        }
    }

    public static void ProcessParentPropertyChangedEvent(object sender, EventArgs e) {
        // ^^^^^^ 这是静态方法,无法访问当前实例的this
        //
        // 当Manager.Salary触发该方法时,sender类型是Manager,事件参数e只能携带Manager的信息,完全不知道所属Team的存在
        //
        // 这个方法里没有任何途径能获取当前经理所属的Team实例         
    }
}
public class Manager {
    private int _salary;
    public int Salary {
        get {
            return this._salary;
        }
        set {
            this._salary = value;
            NotifyParentPropertyChanged();
        }
    }
}

事件可以正常触发,但由于回调方法是static类型,无法获取到刚更新了经理薪资的Team实例引用。Player类也存在类似问题:由于重算逻辑位于静态方法中,无法通知Team类有球员的薪资发生了变更。

除此之外,球队新增或移除球员时也需要触发球队成本重算。我曾尝试使用INotifyCollectionChanged实现该逻辑,但没有成功——最终还是会落到静态方法中,无法获取需要应用变更的目标实例信息。

已评估的可选方案

我认为目前可选方案如下:

  • 加入指向父元素的反向引用。该方案看起来实现直接,但存在很多潜在风险,垃圾回收就是其中之一。内存中预计会有近100万个元素,我不确定这种全双向连接图的结构性能表现如何。
  • 中介者(Mediator)模式。我研究过该模式,但认为它需要大量容易出错的登记逻辑,无法很好解决问题。同时该模式要求在所有数据元素中注入Mediator类,还需要为.NET基础泛型集合类型编写扩展——我不希望被迫使用自定义集合类型替代常用的List<Player>,尤其是如果忘记使用自定义集合,会出现重算值错误这类隐蔽且无报错的bug。

我还有什么可选方案?难道必须在整个模型中添加大量反向引用吗?

是否存在我不了解的其他事件处理模型?我之前一直使用INotifyPropertyChanged是因为在WPF开发中已经习惯了该接口,而且它的性能非常好——在我的机器上执行100万次调用仅需60ms。不过我不确定是否存在其他事件处理范式可以解决这类场景。

这类问题看起来非常基础,肯定早就有人遇到并提出了解决方案,非常感谢大家的解答!


内容的提问来源于stack exchange,提问作者James Cronen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 09:33:38