Правильно копировать структуры C# с (байтовыми) массивами в них?

из того, что я понимаю, при назначении переменной struct другой, первый обычно копируется вместо создания ссылки:

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

это работает просто отлично, выход:

1
2

однако, если у меня есть байт[] внутри моей структуры, это поведение изменяется:

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

это выход:

2
2

как я могу избежать этого? Мне действительно нужно работать с скопировать на полное структура, включая любые массивы байтов.

спасибо! ♪


Edit: Спасибо за вашу помощь! Чтобы глубоко скопировать мою структуру, я теперь использую этот код:

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

это должно называться так:

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

это, кажется, работает нормально, хотя я знаю, что это грязный код.

однако, поскольку структуры, с которыми я работаю, имеют более 50 byte[] несколько другие структуры в них, это просто слишком много работы, чтобы писать Copy()/Clone() методы для каждого из них.

предложения по улучшению кода, конечно, очень приветствуются.

7 ответов


Я не могу найти ссылку, но во втором случае вы просто копируете адрес массива, а не весь массив.

вам нужно сделать глубокую копию, где вы также копируете содержимое массива.


вам придется создать Clone способ, чтобы сделать глубокую копию членов структуры:

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

вот перегрузка для вашего метода копирования структуры, которая не требует приведения результатов:

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

вы называете это так:

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

вы даже можете использовать var:

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

Если вы хотите, чтобы структура инкапсулировала массив по значению (или, по крайней мере, вела себя так, как будто это так), так что копирование структуры будет копировать массив, у вас есть четыре варианта, которые я вижу:

  1. если массив имеет фиксированный размер, объявите структуру "небезопасной" и используйте в ней "фиксированный" массив. "Фиксированный" массив хранится как часть содержащей структуры, поэтому копирование структуры будет копировать массив.
  2. если массив имеет небольшой фиксированный размер, но вы не если вы хотите использовать фиксированный код, вы можете объявить поле для каждого элемента массива, а затем написать индексированное свойство, которое читает или записывает одно из полей структуры. Это, вероятно, лучший подход, если массив имеет около 4 элементов, но это будет непрактично, если он имеет сотни или тысячи.
  3. структура может содержать частную ссылку на массив, который никогда не будет изменен; любая операция, которая изменит массив, должна сделать копию массива, изменить эта копия, а затем перезаписать конфиденциальную ссылку со ссылкой на новый массив (выполните шаги в таком порядке). Этот подход может быть эффективным, если структура будет широко скопирована, но массив редко будет изменен.
  4. напишите класс, который ведет себя как неизменяемый массив и который содержит метод, который будет генерировать новый экземпляр, если задан старый экземпляр, индекс элемента и новое значение, которое будет храниться в этом элементе. Индексатор структуры выглядеть примерно так, как показано ниже, реальная сложность будет в классе.
byte this[int n] {
  get {return myContents[n];}
  set {myContents = myContents.WithValue(n, value);}
}

подход #4 С соответственно-объект проектирования-держатель класса может достичь o(LG с(Н)) для операций чтения и записи выполняются в произвольной последовательности, или, может быть, удастся достичь O(1) производительность для чтения и записи, выполняемых в определенной закономерности (например, "написать" способ может просто добавить каждого индекса и значением для связанного списка обновлений пока либо количество обновлений превышает размер массива или попытке чтения элемента, а затем создать новый массив со всеми обновлениями; такой класс будет работать медленно, если он был попеременно читал и писал, но общее время выполнения П обновления Далее следуют N говорится будет о(n), т. е. среднее время на обновление или чтение будет O(1).


да, но byte[] является ссылочным типом. Следовательно, в структуре хранится только ссылка (указатель) (структура является типом значения). При копировании структуры копируется только ссылка.

вам нужно создать новый byte[] и скопировать данные.


скопировать все byte[] в классе вы можете использовать отражение.

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

использование:

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]);

дает: "1, 2, 3, 2"

обратите внимание, что я использовал свойства вместо членов для массивов, но вы также можете использовать отражение для переменных-членов.


один умнее решение, заимствованные из здесь:

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

помимо безопасности типа из-за использования шаблона, а также сохранения двух функций, это требует, чтобы связать SerializableAttribute на structs( или классы); CLR-aware двоичная (de) сериализация намного лучше, чем слепое копирование необработанных байтов, я думаю:

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