views:

77

answers:

5

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 course very welcome.

+2  A: 

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.

ChrisF
is this a valid reason to overload the assignment operator?
Matt Ellen
+1  A: 

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 };
    }
}
Lee
Wow, so if I have a struct with, let's say 70 byte[], I have to write custom code for every single one of them?
Buoysel
@buoysel - Yes you would, although it is not a good idea to create such a struct. Structs are meant to be used for small (<= 16 bytes) immutable value types.
Lee
Thanks for the tip. I’m working with a few KBs of raw binary data and need to access parts of it in a convenient way, that’s why I’m using such a struct. How else would you do this?
Buoysel
byte arrays are quite convenient for binary data, you don't have to put them in a struct, you can have the arrays in a regular class.
Albin Sunnanbo
A: 

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.

Albin Sunnanbo
A: 

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>(...);
Lasse V. Karlsen
Good idea, thanks!
Buoysel
A: 

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.

Albin Sunnanbo