PropertyGrid撤销功能异常:栈中对象随修改同步变化原因咨询
嘿,这个问题的核心原因其实是C#中引用类型的工作机制,我来给你讲明白:
你定义的Employee是一个引用类型,当你把propertyGridObject推入栈_stack.Push(propertyGridObject)时,栈里存储的并不是这个对象本身,而是指向内存中该对象的引用地址。也就是说,栈里的“对象”和PropertyGrid绑定的propertyGridObject其实指向的是同一个内存实例。所以当你在PropertyGrid里修改属性时,本质上是在修改这个内存实例的内容,栈里的引用指向的还是同一个实例,自然会同步变化。
解决方案:保存对象的独立副本
要实现撤销功能,你需要把对象的副本推入栈,而不是原对象的引用。这里有几种常见的实现方式:
1. 手动实现克隆方法(推荐,性能可控)
在Employee类中添加一个Clone方法,创建一个新的Employee实例并复制所有属性:
public class Employee { [Browsable(false)] public int employeeKey { get; set; } [DisplayName("Name")] [Description("The name of the employee.")] public string employeeName { get; set; } [DisplayName("Active")] [Description("Indicates whether the employee is currently employed.")] public bool employeeIsActive { get; set; } // 手动实现克隆,创建完全独立的对象副本 public Employee Clone() { return new Employee { employeeKey = this.employeeKey, employeeName = this.employeeName, employeeIsActive = this.employeeIsActive }; } }
然后修改frmProperties_Load方法,推入克隆后的对象:
private void frmProperties_Load(object sender, EventArgs e) { _stack = new Stack<object>(); // 推入对象的副本,而非原引用 if (propertyGridObject is Employee emp) { _stack.Push(emp.Clone()); } else { // 针对其他类型可以扩展拷贝逻辑,或直接推入原对象(如果是值类型) _stack.Push(propertyGridObject); } propertyGrid.SelectedObject = propertyGridObject; }
2. 序列化反序列化实现深拷贝
如果你的类结构比较复杂,手动克隆太麻烦,可以用序列化的方式实现深拷贝(比如System.Text.Json):
using System.Text.Json; private void frmProperties_Load(object sender, EventArgs e) { _stack = new Stack<object>(); // 通过序列化->反序列化生成独立副本 var jsonString = JsonSerializer.Serialize(propertyGridObject); var clonedObject = JsonSerializer.Deserialize(jsonString, propertyGridObject.GetType()); _stack.Push(clonedObject); propertyGrid.SelectedObject = propertyGridObject; }
3. 使用MemberwiseClone(浅拷贝)
如果Employee的所有属性都是值类型(比如int、bool)或不可变引用类型(比如string),可以用MemberwiseClone实现浅拷贝:
public class Employee { // ... 原有属性 ... public Employee Clone() { // MemberwiseClone会创建新实例,复制所有值类型字段;引用类型字段会复制引用 return (Employee)this.MemberwiseClone(); } }
注意:如果类中有其他引用类型的属性(比如另一个自定义类),浅拷贝会导致副本和原对象共享这些引用,此时需要改成深拷贝。
验证撤销功能
修改后,当你在PropertyGrid中修改属性,栈里保存的是修改前的独立副本,点击btnReset时,PropertyGrid会加载栈中的副本,就能实现正确的撤销效果了。
内容的提问来源于stack exchange,提问作者tfeitsma

