在C#中修改属性成员时如何触发Setter方法?
问题
我有一个Character类,包含Stat类型的属性。每个角色的Character.Speed属性(类型为Stat)与float类型的ActionValue密切相关,修改速度时需要同步调整ActionValue,机制和《崩坏:星穹铁道》的速度系统一致。现有代码如下:
public class Character { private Stat speed; public Stat Speed { get { // 这里复制speed是因为我觉得不复制的话会返回引用?如果错了请纠正 Stat res = new Stat(flatBonus: speed.FlatBonus, percentageBonus:speed.PercentageBonus, baseValue:speed.BaseValue); foreach (var statusEffect in speed.StatusEffects) { res.AddStatusEffect(statusEffect); } return res; } set { float avOld = ActionValue; float spdOld = this.speed.GetFinalValue(); this.speed = value; ActionValue = avOld * spdOld / speed.GetFinalValue(); } } // ... }
现在的问题是:Setter只有在Speed的引用改变时才会触发,修改Speed的成员属性(比如FlatBonus)时不会触发Setter。请问有没有我不知道的C#特性,能在修改属性的成员时也触发Setter?也就是不仅在执行character.Speed = ...时触发,执行character.Speed.FlatBonus = ...时也能触发。
解决方案
C#没有原生特性可以直接监听对象内部成员的修改并触发外部属性的Setter。要实现你要的效果,得从Stat类的设计入手,提供可控的修改方式并通知外部:
- 让
Stat类成为不可变类型:每次修改FlatBonus、PercentageBonus等属性时,返回一个新的Stat实例。这样修改速度属性的成员时,必须通过character.Speed = character.Speed.WithFlatBonus(newValue)这类方式,自然会触发Speed的Setter,同步调整ActionValue。示例代码如下:
public class Stat { public float BaseValue { get; } public float FlatBonus { get; } public float PercentageBonus { get; } // 其他成员... public Stat(float baseValue, float flatBonus, float percentageBonus) { BaseValue = baseValue; FlatBonus = flatBonus; PercentageBonus = percentageBonus; } public Stat WithFlatBonus(float newFlatBonus) { return new Stat(BaseValue, newFlatBonus, PercentageBonus); } // 同理添加WithPercentageBonus、WithBaseValue等方法 }
- 给
Stat类添加事件通知:在Stat的成员属性Setter中触发事件,Character类订阅这个事件,当Stat的内部属性改变时,自动同步ActionValue。示例:
public class Stat { private float _flatBonus; public float FlatBonus { get => _flatBonus; set { if (_flatBonus != value) { _flatBonus = value; OnStatChanged?.Invoke(this, EventArgs.Empty); } } } // 其他属性同理添加Setter和事件触发 public event EventHandler OnStatChanged; } public class Character { private Stat _speed; private float _lastSpeedValue; public float ActionValue { get; set; } public Stat Speed { get => _speed; set { if (_speed != null) { _speed.OnStatChanged -= HandleStatChanged; } float avOld = ActionValue; float spdOld = _speed?.GetFinalValue() ?? 0; _speed = value; if (_speed != null) { _speed.OnStatChanged += HandleStatChanged; _lastSpeedValue = _speed.GetFinalValue(); ActionValue = avOld * spdOld / _lastSpeedValue; } } } private void HandleStatChanged(object sender, EventArgs e) { float spdNew = _speed.GetFinalValue(); ActionValue = ActionValue * _lastSpeedValue / spdNew; _lastSpeedValue = spdNew; } }
另外要注意:你当前的Speed属性Getter返回的是Stat的副本,这会导致外部修改副本的成员时,完全不会影响到Character内部的_speed字段——就算用事件也没用,因为修改的是副本而非内部实例。如果要保留Getter返回副本的逻辑,那只能用不可变Stat的方案,因为外部必须通过替换整个Speed属性来修改值。
内容的提问来源于stack exchange,提问作者Lnio Yarschov
相关产品推荐
相关产品推荐

