C#自动结构的深层副本(C# Automatic deep copy of struct)

2019-06-26 01:01发布

我有一个结构, MyStruct ,具有专用构件private bool[] boolArray; 和方法ChangeBoolValue(int index, bool Value)

我有一个类, MyClass ,具有场public MyStruct bools { get; private set; } public MyStruct bools { get; private set; }

当我从一个现有的创建一个新对象MYSTRUCT,然后再应用方法ChangeBoolValue(),在这两个对象的布尔阵列改变时,因为参考,不是被称为,被复制到新对象。 例如:

MyStruct A = new MyStruct();
MyStruct B = A;  //Copy of A made
B.ChangeBoolValue(0,true);
//Now A.BoolArr[0] == B.BoolArr[0] == true

是否有强制复制来实现更深层次的复制方式,还是有实现这个不会有同样的问题的方法吗?

我曾专门作出MYSTRUCT一个结构,因为它是值类型,而我不想引用传播。

Answer 1:

运行时执行结构的快速内存拷贝而据我所知,这是不可能的引入或强制自己复制过程他们。 你可以介绍自己的Clone方法甚至一个拷贝构造函数,但你不能强制他们使用它们。

你最好的选择,如果可能的话,使你的结构不变(或不可变类),或在一般的重新设计,以避免此问题。 如果您是API的唯一消费者,那么也许你可以保持格外警惕。

乔恩斯基特(​​和其他人)描述这个问题,虽然会有例外,一般地讲:可变的结构是邪恶的。 结构可以包含引用类型的字段



Answer 2:

一个简单的方法,使一(深)的副本,虽然不是最快的(因为它使用反射),是使用BinaryFormatter原始对象序列化到一个MemoryStream ,然后从反序列化MemoryStream到一个新的MyStruct

    static public T DeepCopy<T>(T obj)
    {
        BinaryFormatter s = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream())
        {
            s.Serialize(ms, obj);
            ms.Position = 0;
            T t = (T)s.Deserialize(ms);

            return t;
        }
    }

工程类和结构。



Answer 3:

作为一种变通方法,我要实现以下。

有2种方法可以修改内容的结构BoolArray 。 而不是创建阵列时该结构被复制,BoolArray将被重新创建时的呼叫改变它是由,如下

public void ChangeBoolValue(int index, int value)
{
    bool[] Copy = new bool[4];
    BoolArray.CopyTo(Copy, 0);
    BoolArray = Copy;

    BoolArray[index] = value;
}

虽然这将为所涉及的BoolArray的太大变化的任何用途是坏的,我使用结构的很多复制的,很少变化。 基准需要改变时,这将只改变到阵列。



Answer 4:

为了避免怪异的语义,它拥有一个可变的引用类型的字段的任何结构必须做两件事情之一:

  1. 它应该非常清楚地表明,从它的角度来看,该领域的内容服务不“持有”的对象,而只是为了识别一个。 例如,一个`KeyValuePair <字符串,控制>`将是完全合理的类型,因为虽然`Control`是可变的,通过这种类型的引用的控制的将是不可变的。
  2. 所述可变对象必须是一个由所述值类型创建的,绝不会外面露出。 此外,之前的对象的引用被存储到该结构的任何字段必须被执行的将永远在所述不可变的对象执行任何突变。

正如其他人指出,一个办法可以让一个结构来模拟阵列将它保存数组,并作出数组的元素被修改任何时候一个新的副本。 这样的事情,当然,是悍然缓慢。 另一种方法是添加一些逻辑存储的最后几个突变请求的指标和值; 任何时候试图读取数组,检查值是否是最近写者之一,如果是这样,使用存储在结构,而不是一个数组中的值。 一旦所有的结构中的“槽”的被填满,使数组的一个副本。 这种做法充其量“只是”提供了一个恒定的速度向上与再生的数组,如果更新击中许多不同的元素,但可能是有益的,如果非常绝大多数更新创下了少量的元件。

当更新很可能有较高的特殊的浓度,但打了太多的元素为他们的结构内完全适应另一种方法,是保持一个参考的“主”阵列,以及一个“更新”阵列一起的整数,指示主阵列的“更新”阵列表示的哪个部分。 更新会经常需要“更新”阵列的再生,但可能是比主阵列要小得多; 如果“更新”阵列变得太大,主阵列可以通过在其内注册成立了“更新”阵列表示变化而再生。

任何这些方法的最大的问题是,虽然struct可以以这样的方式被设计为呈现一致的值类型的语义,同时允许有效的复制,在结构的代码一眼就很难做出明显(与平纹相比,旧的数据结构,其中该结构有一个叫做公共领域的事实Foo非常清楚如何Foo的行为)。



Answer 5:

我想相关的值类型类似的问题,并发现了一个“解决方案”这一点。 你看,你不能更改默认的拷贝构造函数在C#中像您可以在C ++中,因为它的意图是重量轻,无侧影响,。 不过,你所能做的就是等待,直到你真正访问结构,然后检查是否被复制。

这里的问题是,不像引用类型,结构并没有真正的身份; 只有通过价值相等。 然而,他们仍然必须存放在存储器的一些地方,这个地址可以用来识别(虽然只是暂时的)值类型。 在GC是这里的关注,因为它可以随意移动的物体,因此改变在该结构所在的地址,所以你必须能够应付与(例如使该结构的数据保密)。

在实践中,可以从获得该结构的地址this参考,因为它是一个简单的ref T中值类型的情况下。 我离开的手段来获得我的库的引用地址,但它是非常简单发出的定制CIL了点。 在这个例子中,我创造的东西本质上是BYVAL阵列。

public struct ByValArray<T>
{
    //Backup field for cloning from.
    T[] array;

    public ByValArray(int size)
    {
        array = new T[size];
        //Updating the instance is really not necessary until we access it.
    }

    private void Update()
    {
        //This should be called from any public method on this struct.
        T[] inst = FindInstance(ref this);
        if(inst != array)
        {
            //A new array was cloned for this address.
            array = inst;
        }
    }

    //I suppose a GCHandle would be better than WeakReference,
    //but this is sufficient for illustration.
    static readonly Dictionary<IntPtr, WeakReference<T[]>> Cache = new Dictionary<IntPtr, WeakReference<T[]>>();

    static T[] FindInstance(ref ByValArray<T> arr)
    {
        T[] orig = arr.array;
        return UnsafeTools.GetPointer(
            //Obtain the address from the reference.
            //It uses a lambda to minimize the chance of the reference
            //being moved around by the GC.
            out arr,
            ptr => {
                WeakReference<T[]> wref;
                T[] inst;
                if(Cache.TryGetValue(ptr, out wref) && wref.TryGetTarget(out inst))
                {
                    //An object is found on this address.
                    if(inst != orig)
                    {
                        //This address was overwritten with a new value,
                        //clone the instance.
                        inst = (T[])orig.Clone();
                        Cache[ptr] = new WeakReference<T[]>(inst);
                    }
                    return inst;
                }else{
                    //No object was found on this address,
                    //clone the instance.
                    inst = (T[])orig.Clone();
                    Cache[ptr] = new WeakReference<T[]>(inst);
                    return inst;
                }
            }
        );
    }

    //All subsequent methods should always update the state first.
    public T this[int index]
    {
        get{
            Update();
            return array[index];
        }
        set{
            Update();
            array[index] = value;
        }
    }

    public int Length{
        get{
            Update();
            return array.Length;
        }
    }

    public override bool Equals(object obj)
    {
        Update();
        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        Update();
        return base.GetHashCode();
    }

    public override string ToString()
    {
        Update();
        return base.ToString();
    }
}

var a = new ByValArray<int>(10);
a[5] = 11;
Console.WriteLine(a[5]); //11

var b = a;
b[5]++;
Console.WriteLine(b[5]); //12
Console.WriteLine(a[5]); //11

var c = a;
a = b;
Console.WriteLine(a[5]); //12
Console.WriteLine(c[5]); //11

正如你所看到的,这个值类型的行为完全一样,如果底层阵列每当参考阵列复制时复制到新的位置。

警告!!! 只有在你自己的风险在生产代码中使用此代码,最好永远。 这种技术是错误的,邪恶的,在这么多的水平,因为它假定身份的东西,不应该有它。 虽然这种尝试为这个结构(下称“不择手段”),以“执行”值类型的语义,当然也有在几乎任何情况下,真正的问题更好的解决方案。 同时请注意,尽管我试图预见任何这方面的问题,可预见的,有可能是情况下,这种类型将呈现一个相当意外的行为。



文章来源: C# Automatic deep copy of struct