在不中断引用的情况下覆盖引用类型

Overwrite reference type without breaking references

提问人:arcadeperfect 提问时间:4/27/2022 最后编辑:slugsterarcadeperfect 更新时间:4/27/2022 访问量:249

问:

我正在尝试编写一个系统,用于在运行时从磁盘加载一堆变量。对于 Unity 游戏。

我有一个包装类,允许将浮点数、整数等作为引用传递:

public class RefProperty<T>
{
    private T val;
    public T Val
    {
        get => val;
        set => val = value;
    }
    
    public RefProperty()
    {
    }
    public RefProperty(T value)
    {
        Val = value;
    }
}

我想在不破坏引用的情况下用从磁盘加载的覆盖。在下文中,将更新 的值,但在修改时将不再更新。valtarget = sourcetargetreferencetarget

public class Program
{
    private RefProperty<float> source = new RefProperty<float>();
    private RefProperty<float> target = new RefProperty<float>();
    private RefProperty<float> reference = new RefProperty<float>();

    public void Run()
    {
        target.Val = 1;
        reference = target;
        Debug.Log(reference.Val); // returns 1
        
        source.Val = 5;
        target = source;
        Debug.Log(reference.Val); // returns 1
        
        ///////////////////////////////
        
        target.Val = 1;
        reference = target;
        Debug.Log(reference.Val); // returns 1
        
        source.Val = 5;
        target.Val = source.Val;
        Debug.Log(reference.Val); // returns 5
    }
}

第二种方法确实有效。但是,我试图避免这种情况,因为最终我想遍历一个类的字段,其中包含具有不同类型的负载,以及一些子类,并替换所有值,而不必知道类型:target.Val = source.Val

public class RefPropertyChild : RefProperty<float>
{
    // float specific features
    public RefPropertyChild(float value)
    {
        Val = value;
    }
}


public class Parameters
{
    public RefProperty<bool> p1 = new RefProperty<bool>(true);
    public RefProperty<int> p2 = new RefProperty<int>(5);
    public RefPropertyChild p3 = new RefPropertyChild(6);
}


public class LoadData
{
    private Parameters sourceParams;
    private Parameters targetParams;

    void load()
    {
        foreach (FieldInfo field in typeof(Parameters).GetFields())
        {
            field.SetValue(targetParams, field.GetValue(sourceParams));
        }
    }
}

和以前一样,这确实修改了值,但破坏了引用,我假设是因为它现在指向不同的内存位置?有什么方法可以做到这一点,或者我是否必须显式处理每种类型并使用 getter / setters?

C# Unity-Game-Engine 反射 按引用传递

评论

0赞 Ralf 4/27/2022
无论如何,使用属性似乎是方法。我建议在 Parameters 中有一个可以序列化/反序列化自身的内部类。一个针对序列化而优化的类,可以这么说,它的引用更改无关紧要。然后,外部类 Parameters 仅将其属性转发给内部类属性。某种立面图案。

答:

1赞 JonasH 4/27/2022 #1

一种解决方案是将引用替换为 ID,并将实际对象存储在其他地方。如果在序列化时使用某种 DTO 来表示对象,这可能更易于管理。像这样:

long maxId = 0;
var myReferences = new Dictionary<object, long>();
foreach(var obj in myObjects){
    var r = obj.MyReferenceProperty;
    var id = myReferences.TryGetValue(r, out var t) ? t: myReferences[r] = maxId++;
     
     Use id in place of your property in the DTO or serialization format.
}

// Serialize the myReferences dictionary

反序列化时,您将执行相反的操作。反序列化引用列表,并将该属性替换为相应的引用对象。在执行字典序列化时,可能需要某种方法来包含类型信息,以便能够将属性反序列化为正确的类型。

某些序列化库内置了对此的支持。Protobuf .Net 具有 AsReference 属性,但看起来该属性已过时

另一种选择是在运行时使用某种基于 id 的系统。因此,对象将共享一个公共的引用管理器对象。每当您需要读取或写入公共属性的值时,您都会将您的 ID 交给引用管理器并取回该值。这使得序列化更容易,但会使实现更加复杂。