Правильно ли копировать структуры C # с (байтовыми) массивами в них? - PullRequest
6 голосов
/ 21 августа 2010

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

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

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

Спасибо! ♪


Редактировать: Спасибо за вашу помощь! Для глубокого копирования моей структуры я сейчас использую этот код:

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 ]

3 голосов
/ 21 августа 2010

Вам потребуется создать метод 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 };
    }
}
2 голосов
/ 15 июля 2013

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

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

Подход № 4 с соответствующим образом спроектированным классом объекта-держателя может достигать производительности O (Lg (N)) как для операций чтения, так и записи, выполняемых в произвольной последовательности, или может достигать производительности O (1) для операций чтения и записи. выполняется в определенных шаблонах (например, метод «запись» может просто добавлять каждый индекс и значение в связанный список обновлений до тех пор, пока либо число обновлений не превысит размер массива, либо не будет предпринята попытка прочитать элемент, а затем может быть создано новый массив со всеми примененными обновлениями, такой класс будет работать медленно, если он будет поочередно считываться и записываться, но общее время выполнения N обновлений, за которыми следует N чтений, будет O (N), что означает среднее время на обновление или чтение быть O (1).

2 голосов
/ 21 августа 2010

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

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>(...);
2 голосов
/ 21 августа 2010

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

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

1 голос
/ 21 августа 2010

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

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

0 голосов
/ 15 июля 2013

Один умнее Обходной путь , заимствованный из здесь :

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 с struct (или классами); Я думаю, что двоичная (де) сериализация с поддержкой CLR намного лучше, чем слепое копирование необработанных байтов:

[Serializable]
struct test
{
    public int[] data;
}
0 голосов
/ 21 августа 2010

Чтобы скопировать все 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"

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

...