为何PropertyInfo.SetValue会修改源字典值?如何规避?
你遇到的情况是:用静态字典做缓存,把字典里的Country对象赋值给Person的Country属性后,调用encodeObj方法修改这个对象的字符串属性时,发现字典里的原缓存值也被改了,就算复制字符串也没用。
你的代码示例:
Person p = new Person(); //Cache.Countries是存储国家信息的静态字典 //每个Country包含多个属性,不止description p.Country = Cache.Countries[1]; encodeObj(p.Country); private void encodeObj(object obj) { foreach(var pi in obj.GetType().GetProperties()) { object propValue = pi.GetValue(obj,null); if(pi.PropertyType == typeof(string) && !string.IsNullOrEmpty((string)propValue)) { //尝试复制字符串但无效 string temp = string.Copy((string)propValue); string encoded = WebUtility.HtmlEncode(temp); //执行前Cache.Countries[1]对应属性值正常 pi.SetValue(obj,encoded); //执行后Cache.Countries[1]对应属性值已变为编码后的值! } } }
嘿,这个问题我太熟了——你踩的是引用类型传递的经典坑!
为什么复制字符串没用?
因为你复制的只是字符串本身,但问题根本不在字符串上:p.Country = Cache.Countries[1]这行代码并没有创建新的Country对象,只是把字典里那个对象的内存引用赋值给了p.Country。也就是说,p.Country和Cache.Countries[1]指向的是同一个内存里的对象,你修改p.Country的任何属性,本质上都是在修改字典里的那个原对象,复制字符串当然没用啦。
怎么解决?
核心思路就是:给p.Country一个独立的Country对象副本,让它和字典里的缓存对象彻底断开引用。这里有几种常用方法:
1. 给Country类手动实现克隆方法
如果Country的属性不多,手动写一个Clone方法最直接:
public class Country { // 假设你的Country类有这些属性 public string Description { get; set; } public string Code { get; set; } // 其他属性... // 实现克隆方法,复制所有属性到新对象 public Country Clone() { return new Country { Description = this.Description, Code = this.Code, // 把所有属性都复制一遍 }; } } // 赋值的时候用克隆后的对象 p.Country = Cache.Countries[1].Clone();
2. 用序列化做通用深拷贝
如果Country属性很多,或者你不想手动写克隆逻辑,可以用序列化的方式做深拷贝(比如用Newtonsoft.Json):
using Newtonsoft.Json; // 写一个通用的深拷贝工具方法 public static T DeepCopy<T>(T obj) { if (obj == null) return default; string jsonStr = JsonConvert.SerializeObject(obj); return JsonConvert.DeserializeObject<T>(jsonStr); } // 使用的时候 p.Country = DeepCopy<Country>(Cache.Countries[1]);
这种方法会把对象序列化成JSON字符串,再反序列化成新对象,相当于创建了一个完全独立的副本。
3. 如果是值类型(struct)直接赋值就行
不过看你的代码,Country应该是类(引用类型),如果是struct的话,直接赋值就是值复制,不会有这个问题,但业务场景里国家信息一般都是类,所以这个方法可能不适用。
总结
只要让p.Country指向一个和字典缓存无关的新对象,再去修改它的属性,就不会影响到原缓存里的值了。
内容的提问来源于stack exchange,提问作者Batsheva

