开发者

Properly copy C# structs with (byte) arrays in them?

From what I understand, when assigning a struct variable to another one, the first one is usually copied instead of creating a reference:

public struct MYSTRUCT1
{
    public byte val1;
}
// (...)
public DoSomething() {
    MYSTRUCT1 test1;
    test1.val1 = 1;
    MYSTRUCT1 test2 = test1;
    test2.val1 = 2;

    Console.WriteLine(test1.val1);
    Console.WriteLine(test2.val1);
}

This works just fine, the output is:

1
2

However, if I have a byte[] inside my struct, this behaviour changes:

public struct MYSTRUCT1
{
    public byte[] val1;
}
// (...)
public DoSomething() {
    MYSTRUCT1 test1;
    test1.val1 = new byte[0x100];
    test1.val1[0] = 1;
    MYSTRUCT1 test2 = test1;
    test2.val1[0] = 2;

    Console.WriteLine(test1.val1[0]);
    Console.WriteLine(test2.val1[0]);
}

This is the output:

2
2

How can I avoid this? I really need to work with a copy of the complete struct including any byte arrays.

Thank you! ♪


Edit: Thanks for all your help! In order to deep copy my struct, I’m now using this code:

public static object deepCopyStruct(object anything, Type anyType)
{
    return RawDeserialize(RawSerialize(anything), 0, anyType);
}

/* Source: http://bytes.com/topic/c-sharp/answers/249770-byte-structure */
public static object RawDeserialize(byte[] rawData, int position, Type anyType)
{
    int rawsize = Marshal.SizeOf(anyType);
    if (rawsize > rawData.Length)
        return null;
    IntPtr buffer = Marshal.AllocHGlobal(rawsize);
    Marshal.Copy(rawData, position, buffer, rawsize);
    object retobj = Marshal.PtrToStructure(buffer, anyType);
    Marshal.FreeHGlobal(buffer);
    return retobj;
}

/* Source: http://bytes.com/topic/c-sharp/answers/249770-byte-structure */
public static byte[] RawSerialize(object anything)
{
    int rawSize = Marshal.SizeOf(anything);
    IntPtr buffer = Marshal.AllocHGlobal(rawSize);
    Marshal.StructureToPtr(anything, buffer, false);
    byte[] rawDatas = new byte[rawSize];
    Marshal.Copy(buffer, rawDatas, 0, rawSize);
    Marshal.FreeHGlobal(buffer);
    return rawDatas;
}

It must be called like this:

MYSTRUCT1 test2 = (MYSTRUCT1)deepCopyStruct(test1, typeof(MYSTRUCT1));

This seems to work fine, though I’m aware that this is dirty code.

However, since the structs I’m working with have over 50 byte[] several other structs in them, it’s just too much work to write Copy()/Clone() methods for each of them.

Suggestions for better code are of co开发者_运维问答urse very welcome.


You will have to create a Clone method to do a deep copy of the struct's members:

public struct MyStruct
{
    public byte[] data;
    public MyStruct Clone()
    {
        byte[] clonedData = new byte[this.data.Length];
        data.CopyTo(clonedData, 0);

        return new MyStruct { data = clonedData };
    }
}


I can't find the reference, but in the second case you are just copying the address of the array rather than the whole array.

You need to do a deep copy where you copy the contents of the array as well.


Here's an overload for your struct copy method that doesn't require casting the results:

public static T RawDeserialize<T>(byte[] rawData, int position)
{
    return (T)RawDeserialize(rawData, position, typeof(T));
}

You call it like this:

MYSTRUCT1 x = RawDeserialize<MYSTRUCT1>(...);

You can even use var:

var x = RawDeserialize<MYSTRUCT1>(...);


If you want to have a struct encapsulate an array by value (or at least behave as though it does so), such that copying the struct will copy the array, you have four choices that I can see:

  1. If the array is of fixed size, declare the struct as `unsafe` and use a `fixed` array within it. A `fixed` array is stored as part of the containing struct, so copying the struct will copy the array.
  2. If the array is of small fixed size, but you don't want to use fixed code, you could declare a field for each element of the array, and then write an indexed property which reads or writes one of the struct's fields as appropriate. This is probably the best approach if the array has about 4 elements, but it will be impractical if it has hundreds or thousands.
  3. The struct could hold a private reference to an array which will never be modified; any operation which would modify the array should make a copy of the array, modify that copy, and then overwrite the privately-held reference with a reference to the new array (perform the steps in that order). This approach may be efficient if the structure will be widely copied but the array will seldom be modified.
  4. Write a class which behaves like an immutable array and which contains a method that will generate a new instance if given an old instance, an item index, and a new value to be stored in that item. The struct indexer would look something like what's shown below; the real complexity would be in the class.
byte this[int n] {
  get {return myContents[n];}
  set {myContents = myContents.WithValue(n, value);}
}

Approach #4 with a suitably-designed object-holder class may achieve O(Lg(N)) performance for both reads and writes performed in arbitrary sequence, or may be able to achieve O(1) performance for reads and writes performed in particular patterns (e.g. the "write" method could simply add each index and value to a linked list of updates until either the number of updates exceeds the size of the array or an attempt is made to read an item, and could then create a new array with all updates applied; such a class would perform slowly if it was alternately read and written, but the total time to perform N updates followed by N reads would be O(N), meaning the average time per update or read would be O(1).


Yes, but the byte[] is reference type. Hence only a reference (pointer) is stored in the struct (the struct is a value type). When the struct is copied only the reference is copied.

You need to create a new byte[] and copy the data.


To copy all byte[] in a class you can use reflection.

class ArrayContainer
{
    public byte[] Array1 { get; set; }
    public byte[] Array2 { get; set; }

    public ArrayContainer DeepCopy()
    {
        ArrayContainer result = new ArrayContainer();
        foreach (var property in this.GetType().GetProperties())
        {
            var oldData = property.GetValue(this, null) as byte[];
            if (oldData != null)
            {
                // Copy data with .ToArray() actually copies data.
                property.SetValue(result, oldData.ToArray(), null);
            }
        }

        return result;
    }
}

Usage:

ArrayContainer container = new ArrayContainer();
container.Array1 = new byte[] { 1 };
container.Array2 = new byte[] { 2 };
ArrayContainer copy = container.DeepCopy();
copy.Array1[0] = 3;

Console.WriteLine("{0}, {1}, {2}, {3}", container.Array1[0], container.Array2[0], copy.Array1[0], copy.Array2[0]);

Gives: "1, 2, 3, 2"

Note that I have used properties instead of members for the arrays, but you can use reflection for member variables too.


One smarter workaround, borrowed from here:

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;
    }
}

Beside being type safe due to the template usage and also saving you two functions, this requires you to associate a SerializableAttribute to your structs (or classes); CLR-aware binary (de)serialization is far better than blindly copying raw bytes, I think:

[Serializable]
struct test
{
    public int[] data;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜