Правильно копировать структуры 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>(...);
Если вы хотите, чтобы структура инкапсулировала массив по значению (или, по крайней мере, вела себя так, как будто это так), так что копирование структуры будет копировать массив, у вас есть четыре варианта, которые я вижу:
- если массив имеет фиксированный размер, объявите структуру "небезопасной" и используйте в ней "фиксированный" массив. "Фиксированный" массив хранится как часть содержащей структуры, поэтому копирование структуры будет копировать массив.
- если массив имеет небольшой фиксированный размер, но вы не если вы хотите использовать фиксированный код, вы можете объявить поле для каждого элемента массива, а затем написать индексированное свойство, которое читает или записывает одно из полей структуры. Это, вероятно, лучший подход, если массив имеет около 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
на struct
s( или классы); CLR-aware двоичная (de) сериализация намного лучше, чем слепое копирование необработанных байтов, я думаю:
[Serializable]
struct test
{
public int[] data;
}