C#泛型引用类型赋值同步变更:如何避免LastValue随CurrentValue修改?
问题描述
定义了如下泛型Pair类:
public class Pair<T, V> { public T A = default; public V B = default; public Pair() { A = default; B = default; } public Pair(T a, V b) { A = a; B = b; } public override bool Equals(object obj) { Pair<T, V> other = obj as Pair<T, V>; return A.Equals(other.A) && B.Equals(other.B); } public override int GetHashCode() { return base.GetHashCode(); } public override string ToString() { return "Pair: (" + A.ToString() + " , " + B.ToString() + ")"; } }
同时定义了包含Pair类型的类:
public class FakeClass<T> { public T LastValue { get; protected set; } = default; public T CurrentValue = default; public void Execute() { LastValue = CurrentValue; } } public class FakeClassWithPair : FakeClass<Pair<int, int>> { }
执行以下代码后:
FakeClassWithPair fake = new FakeClassWithPair(); fake.CurrentValue.A = 2; fake.CurrentValue.B = 5; fake.Execute(); fake.CurrentValue.A = 32; fake.CurrentValue.B = 53;
调试发现LastValue与CurrentValue的值同步变为32和53,如何避免这种情况?
问题原因
Pair是引用类型(class),执行LastValue = CurrentValue时,只是将CurrentValue的内存引用地址赋值给LastValue,两者指向同一个Pair实例。后续修改CurrentValue.A或CurrentValue.B,本质是修改同一个实例的成员,因此会同步影响LastValue。
解决方案
方案1:将Pair改为不可变类(推荐)
让Pair的成员仅能在构造时初始化,后续无法修改。如需更新数值,必须创建新的Pair实例,从根源上避免引用共享导致的同步问题。
修改后的Pair类:
public class Pair<T, V> { // 改为只读属性,仅通过构造函数赋值 public T A { get; } public V B { get; } public Pair() { A = default; B = default; } public Pair(T a, V b) { A = a; B = b; } // 提供创建新实例的方法,用于更新值 public Pair<T, V> WithA(T newA) { return new Pair<T, V>(newA, B); } public Pair<T, V> WithB(V newB) { return new Pair<T, V>(A, newB); } public override bool Equals(object obj) { Pair<T, V> other = obj as Pair<T, V>; return A.Equals(other.A) && B.Equals(other.B); } public override int GetHashCode() { // 基于成员生成哈希值,保证Equals与GetHashCode逻辑一致 return HashCode.Combine(A, B); } public override string ToString() { return $"Pair: ({A} , {B})"; } }
使用示例:
FakeClassWithPair fake = new FakeClassWithPair(); fake.CurrentValue = new Pair<int, int>(2, 5); fake.Execute(); // 创建新实例替换CurrentValue,不会影响LastValue fake.CurrentValue = new Pair<int, int>(32, 53); // 或者使用With方法局部更新 // fake.CurrentValue = fake.CurrentValue.WithA(32).WithB(53);
方案2:在Execute方法中创建Pair的副本
如果不想修改Pair的可变性,可以在Execute方法中复制CurrentValue的成员,创建新的Pair实例赋值给LastValue。
方法一:给Pair添加克隆方法
public class Pair<T, V> { public T A = default; public V B = default; // 添加克隆方法 public Pair<T, V> Clone() { return new Pair<T, V>(A, B); } // 其余方法保持不变 }
修改FakeClass的Execute方法:
public class FakeClass<T> where T : Pair<int, int> { public T LastValue { get; protected set; } = default; public T CurrentValue = default; public void Execute() { LastValue = (T)CurrentValue.Clone(); } }
方法二:直接在子类中重写Execute
针对特定的Pair<int, int>类型,在FakeClassWithPair中直接创建新实例:
public class FakeClassWithPair : FakeClass<Pair<int, int>> { public void Execute() { LastValue = new Pair<int, int>(CurrentValue.A, CurrentValue.B); } }
方案3:将Pair改为值类型(struct)
把Pair从class改为struct,值类型在赋值时会自动拷贝整个实例,而非引用地址。这样LastValue = CurrentValue会生成一个独立副本,后续修改CurrentValue的成员不会影响LastValue。
修改后的Pair:
public struct Pair<T, V> { public T A; public V B; public Pair(T a, V b) { A = a; B = b; } public override bool Equals(object obj) { if (obj is not Pair<T, V> other) return false; return A.Equals(other.A) && B.Equals(other.B); } public override int GetHashCode() { return HashCode.Combine(A, B); } public override string ToString() { return $"Pair: ({A} , {B})"; } }
注意:值类型默认构造函数无法自定义逻辑,所有成员会自动初始化;如果Pair的成员是引用类型,仍然会共享引用,此时需要额外处理深拷贝。
内容的提问来源于stack exchange,提问作者ProtoTyPus
相关产品推荐
相关产品推荐

