修改Point属性时Box的UpperLHCorner setter未触发,如何同步更新LowerRHCorner?
问题分析与解决方案
这个问题我之前也碰到过,核心原因是引用类型的特性在搞鬼——咱们一步步拆解和解决:
为什么setter没触发?
Point是引用类型,当你写box.UpperLHCorner.X = 10;时,实际执行了两个步骤:
- 调用
UpperLHCorner的getter,拿到内部_upperLHCorner对象的引用 - 通过这个引用直接修改
Point对象的X字段,完全没有触发UpperLHCorner的setter
所以Box里写的更新LowerRHCorner的逻辑根本没机会执行。
下面是几种可行的解决思路,你可以根据场景选择:
方案1:把Point改成不可变类型(推荐)
不可变类型的核心是一旦创建就不能修改属性,只能通过构造新对象来改变值。这样用户就必须重新赋值给UpperLHCorner,自然会触发setter。
修改后的Point类:
class Point { // 只读属性,只能通过构造函数初始化 public double X { get; } public double Y { get; } public Point(double x, double y) { X = x; Y = y; } // 可选:提供便捷方法生成新对象(避免重复写构造函数) public Point WithX(double newX) => new Point(newX, Y); public Point WithY(double newY) => new Point(X, newY); }
使用方式:
// 不再直接修改X,而是生成新的Point对象赋值 box.UpperLHCorner = box.UpperLHCorner.WithX(10);
优点:不可变类型天生线程安全,能避免很多意外的副作用,代码逻辑更清晰。
缺点:需要改变调用方式,不能直接修改X/Y字段。
方案2:在Box中提供专门的修改方法,禁止外部直接修改Point引用
如果不想改动Point的可变性,可以在Box里返回Point的副本,同时提供专门的方法来修改UpperLHCorner的X/Y,内部统一更新LowerRHCorner。
修改后的Box类:
internal class Box { private Point _upperLHCorner = new Point(); private Point _lowerRHCorner = new Point(); private double _width; // 假设你已经定义了_width字段 private double _height; // 假设你已经定义了_height字段 internal Box() { } // 返回Point的副本,防止外部直接修改内部对象 public Point UpperLHCorner { get { return new Point(_upperLHCorner.X, _upperLHCorner.Y); } set { _upperLHCorner = value; UpdateLowerRHCorner(); } } public Point LowerRHCorner { get { return new Point(_lowerRHCorner.X, _lowerRHCorner.Y); } } // 提供专门的方法修改X/Y public void SetUpperLHCornerX(double x) { _upperLHCorner.X = x; UpdateLowerRHCorner(); } public void SetUpperLHCornerY(double y) { _upperLHCorner.Y = y; UpdateLowerRHCorner(); } // 提取更新逻辑到单独方法,方便复用 private void UpdateLowerRHCorner() { _lowerRHCorner.X = _upperLHCorner.X + _width; _lowerRHCorner.Y = _upperLHCorner.Y + _height; } }
使用方式:
// 通过Box提供的方法修改X box.SetUpperLHCornerX(10);
优点:不需要修改Point类,逻辑集中在Box内部。
缺点:用户不能再直接修改UpperLHCorner的属性,必须调用指定方法。
方案3:让Point实现INotifyPropertyChanged接口
如果希望保留用户直接修改UpperLHCorner.X的习惯,可以让Point实现属性变更通知接口,在Box中监听这个事件,自动更新LowerRHCorner。
修改后的代码:
using System.ComponentModel; class Point : INotifyPropertyChanged { private double _x; private double _y; public double X { get => _x; set { if (_x != value) { _x = value; OnPropertyChanged(nameof(X)); } } } public double Y { get => _y; set { if (_y != value) { _y = value; OnPropertyChanged(nameof(Y)); } } } public Point(double x, double y) { _x = x; _y = y; } public Point() {} // 保留无参构造 public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } internal class Box { private Point _upperLHCorner = new Point(); private Point _lowerRHCorner = new Point(); private double _width; private double _height; internal Box() { // 监听UpperLHCorner的属性变化 _upperLHCorner.PropertyChanged += UpperLHCorner_PropertyChanged; } private void UpperLHCorner_PropertyChanged(object sender, PropertyChangedEventArgs e) { // 当X或Y变化时自动更新LowerRHCorner if (e.PropertyName == nameof(Point.X) || e.PropertyName == nameof(Point.Y)) { UpdateLowerRHCorner(); } } public Point UpperLHCorner { get => _upperLHCorner; set { // 先移除旧对象的事件监听,避免内存泄漏 if (_upperLHCorner != null) { _upperLHCorner.PropertyChanged -= UpperLHCorner_PropertyChanged; } _upperLHCorner = value; // 给新对象添加事件监听 if (_upperLHCorner != null) { _upperLHCorner.PropertyChanged += UpperLHCorner_PropertyChanged; } UpdateLowerRHCorner(); } } public Point LowerRHCorner { get => _lowerRHCorner; } private void UpdateLowerRHCorner() { _lowerRHCorner.X = _upperLHCorner.X + _width; _lowerRHCorner.Y = _upperLHCorner.Y + _height; } }
使用方式:
// 可以直接修改X,会自动触发LowerRHCorner更新 box.UpperLHCorner.X = 10;
优点:完全保留用户的使用习惯,不需要改变调用方式。
缺点:代码复杂度略高,需要处理事件订阅和取消,注意避免内存泄漏。
内容的提问来源于stack exchange,提问作者Eric Snyder
相关产品推荐
相关产品推荐

