You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 01:30:57