C#结构体的自动深拷贝

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11336935/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 17:29:39  来源:igfitidea点击:

C# Automatic deep copy of struct

c#structdeep-copydefault-copy-constructor

提问by 3Pi

I have a struct, MyStruct, that has a private member private bool[] boolArray;and a method ChangeBoolValue(int index, bool Value).

我有一个结构体,MyStruct它有一个私有成员private bool[] boolArray;和一个方法ChangeBoolValue(int index, bool Value)

I have a class, MyClass, that has a field public MyStruct bools { get; private set; }

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

When I create a new MyStruct object from an existing one, and then apply method ChangeBoolValue(), the bool array in both objects is changed, because the reference, not what was referred to, was copied to the new object. E.g:

当我从现有对象创建一个新的 MyStruct 对象,然后应用方法 ChangeBoolValue() 时,两个对象中的 bool 数组都会更改,因为引用而不是引用的内容被复制到新对象。例如:

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

Is there a way of forcing a copy to implement a deeper copy, or is there a way to implement this that will not have the same issue?

有没有办法强制一个副本实现更深的副本,或者有没有办法实现这个不会有同样的问题?

I had specifically made MyStruct a struct because it was value type, and I did not want references propagating.

我专门将 MyStruct 设为结构体,因为它是值类型,而且我不希望引用传播。

采纳答案by Chris Sinclair

The runtime performs a fast memory copy of structs and as far as I know, it's not possible to introduce or force your own copying procedure for them. You could introduce your own Clonemethod or even a copy-constructor, but you could not enforce that they use them.

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

Your best bet, if possible, to make your struct immutable (or an immutable class) or redesign in general to avoid this issue. If you are the sole consumer of the API, then perhaps you can just remain extra vigilant.

如果可能,最好的办法是使结构不可变(或不可变的类)或重新设计,以避免出现此问题。如果您是 API 的唯一使用者,那么也许您可以保持格外警惕。

Jon Skeet (and others) have described this issue and although there can be exceptions, generally speaking: mutable structs are evil. Can structs contain fields of reference types

Jon Skeet(和其他人)已经描述了这个问题,尽管可能有例外,但一般来说:可变结构是邪恶的。 结构体可以包含引用类型的字段吗

回答by Eric J.

One simple method to make a (deep) copy, though not the fastest one (because it uses reflection), is to use BinaryFormatterto serialize the original object to a MemoryStreamand then deserialize from that MemoryStreamto a new MyStruct.

制作(深)副本的一种简单方法,虽然不是最快的方法(因为它使用反射),是使用BinaryFormatter将原始对象序列化为 a 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;
        }
    }

Works for classes and structs.

适用于类和结构。

回答by 3Pi

As a workaround, I am going to implement the following.

作为一种解决方法,我将实施以下措施。

There are 2 methods in the struct that can modify the contents of BoolArray. Rather than creating the array when the struct is copied, BoolArray will be created anew when a call to change it is made, as follows

struct 中有 2 个方法可以修改BoolArray. 不是在复制结构时创建数组,而是在调用更改它时重新创建 BoolArray,如下所示

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

    BoolArray[index] = value;
}

Though this would be bad for any uses that involved much change of the BoolArray, my use of the struct is a lot of copying, and very little changing. This will only change the reference to the array when a change is required.

尽管这对于涉及 BoolArray 大量更改的任何用途都是不利的,但我对结构的使用是大量复制,并且几乎没有变化。这只会在需要更改时更改对数组的引用。

回答by supercat

To avoid weird semantics, any struct which holds a field of a mutable reference type must do one of two things:

为了避免奇怪的语义,任何包含可变引用类型字段的结构都必须执行以下两项操作之一:

  1. It should make very clear that, from its perspective, the the content of the field serves not to "hold" an object, but merely to identify one. For example, a `KeyValuePair<String, Control>` would be a perfectly reasonable type, since although `Control` is mutable, the identityof a control referenced by such a type would be immutable.
  2. The mutable object must be one which is created by the value type, will never be exposed outside it. Further, any mutations that will ever be performed upon the immutable object must be performed before a reference to the object is stored into any field of the struct.
  1. 应该非常清楚的是,从它的角度来看,字段的内容不是用来“容纳”一个对象,而只是用来标识一个对象。例如,“KeyValuePair<String, Control>”将是一个完全合理的类型,因为尽管“Control”是可变的,但这种类型所引用的控件的标识将是不可变的。
  2. 可变对象必须是由值类型创建的对象,永远不会暴露在它之外。此外,必须在对对象的引用存储到结构的任何字段之前执行将在不可变对象上执行的任何更改。

As others have noted, one way to allow a struct to simulate an array would be for it to hold an array, and make a new copy of that array any time an element is modified. Such a thing would, of course, be outrageously slow. An alternative approach would be to add some logic to store the indices and values of the last few mutations requests; any time an attempt is made to read the array, check whether the value is one of the recently-written ones and, if so, use the value stored in the struct instead of the one in the array. Once all of the 'slots' within the struct are filled up, make a copy of the array. This approach would at best "only" offer a constant speed up versus regenerating the array if updates hit many different elements, but could be helpful if the extremely vast majority of updates hit a small number of elements.

正如其他人所指出的,允许结构模拟数组的一种方法是让它保存一个数组,并在修改元素时制作该数组的新副本。当然,这样的事情会非常缓慢。另一种方法是添加一些逻辑来存储最后几个突变请求的索引和值;任何时候尝试读取数组时,检查该值是否是最近写入的值之一,如果是,则使用存储在结构中的值而不是数组中的值。一旦结构中的所有“槽”都填满,请复制该数组。如果更新击中许多不同的元素,这种方法最多只能“仅”提供恒定的加速而不是重新生成数组,但如果绝大多数更新击中了少量元素,则可能会有所帮助。

Another approach when updates are likely to have a high special concentration, but hit too many elements for them to fit entirely within a struct, would be to keep a reference to a "main" array, as well as an "updates" array along with an integer indicating what part of the main array the "updates" array represents. Updates would often require regeneration of the "updates" array, but that could be much smaller than the main array; if the "updates" array gets too big, the main array can be regenerated with changes represented by the "updates" array incorporated within it.

当更新可能具有很高的特殊浓度,但遇到太多元素以至于它们无法完全放入一个结构中时,另一种方法是保留对“主”数组以及“更新”数组的引用一个整数,指示“更新”数组代表主数组的哪一部分。更新通常需要重新生成“更新”数组,但这可能比主数组小得多;如果“updates”数组变得太大,主数组可以用包含在其中的“updates”数组表示的更改重新生成。

The biggest problem with any of these approaches is that while the structcould be engineered in such a way as to present consistent value-type semantics while allowing efficient copying, a glance at the struct's code would hardly make that obvious (as compared with plain-old-data structs, where the fact that the struct has a public field called Foomakes it very clear how Foowill behave).

任何这些方法的最大问题是,虽然struct可以以这样一种方式设计,即在允许有效复制的同时呈现一致的值类型语义,但看一眼结构体的代码几乎不会使这一点变得明显(与普通老式相比) -data 结构,其中结构有一个名为公共字段的事实Foo使得它的Foo行为非常清楚)。

回答by IllidanS4 wants Monica back

I was thinking about a similar issue related to value types, and found out a "solution" to this. You see, you cannotchange the default copy constructor in C# like you can in C++, because it's intended to be lightweight and side effects-free. However, what you can do is wait until you actually access the struct, and then check if it was copied.

我正在考虑与值类型相关的类似问题,并找到了一个“解决方案”。您看,您不能像在 C++ 中那样更改 C# 中的默认复制构造函数,因为它旨在实现轻量级和无副作用。但是,您可以做的是等到您实际访问该结构,然后检查它是否被复制。

The problem with this is that unlike reference types, structs have no real identity; there is only by-value equality. However, they still have to be stored at some place in memory, and this address can be used to identify (albeit temporarily) a value type. The GC is a concern here, because it can move objects around, and therefore change the address at which the struct is located, so you would have to be able to cope with that (e.g. make the struct's data private).

问题在于,与引用类型不同,结构没有真正的身份。只有按值相等。但是,它们仍然必须存储在内存中的某个位置,并且该地址可用于标识(尽管是暂时的)值类型。GC 在这里是一个问题,因为它可以移动对象,从而更改结构所在的地址,因此您必须能够应对(例如,将结构的数据设为私有)。

In practice, the address of the struct can be obtained from the thisreference, because it's a simple ref Tin case of a value type. I leave the meansto obtain the address from a reference to my library, but it's quite simple to emit custom CIL for that. In this example, I create something what is essentially a byval array.

实际上,可以从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

As you can see, this value type behaves exactly as if the underlying array was copied to a new location every time the reference to the array is copied.

如您所见,此值类型的行为就像每次复制对数组的引用时将底层数组复制到新位置一样。

WARNING!!!Use this code only at your own risk, and preferably never in a production code. This technique is wrong and evil at so many levels, because it assumes identity for something that shouldn't have it. Although this tries to "enforce" value type semantics for this struct ("the end justifies the means"), there are certainly better solutions to the real problem in almost anycase. Also please note that although I have tried to foresee any foreseeable issues with this, there could be cases where this type will show quite an unexpected behaviour.

警告!!!仅在您自己承担风险的情况下使用此代码,最好不要在生产代码中使用。这种技术在很多层面上都是错误和邪恶的,因为它为不应该拥有的东西假定了身份。尽管这试图为这个结构“强制”值类型语义(“最终证明手段”),但在几乎任何情况下,对于真正的问题肯定有更好的解决方案。另请注意,虽然我已经尝试预见任何可预见的问题,但在某些情况下,这种类型会表现出相当意外的行为。

回答by Arutyun Enfendzhyan

Struct is copied when passed right? So:

传递时会复制结构吗?所以:

public static class StructExts
{
    public static T Clone<T> ( this T val ) where T : struct => val;
}

Usage:

用法:

var clone = new AnyStruct ().Clone ();