Я предложил другой подход, который мог бы преобразовать любой struct
без хлопот с фиксированной длиной, однако полученный байтовый массив мог бы иметь немного больше накладных расходов.
Вот образец struct
:
[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
public MyEnum enumvalue;
public string reqtimestamp;
public string resptimestamp;
public string message;
public byte[] rawresp;
}
Как видите, все эти структуры потребуют добавления атрибутов фиксированной длины. Который часто заканчивал тем, что занимал больше места, чем требовалось. Обратите внимание, что LayoutKind.Sequential
является обязательным, поскольку мы хотим, чтобы отражение всегда давало нам один и тот же порядок при извлечении для FieldInfo
. Мое вдохновение от TLV
Type-Length-Value. Давайте посмотрим на код:
public static byte[] StructToByteArray<T>(T obj)
{
using (MemoryStream ms = new MemoryStream())
{
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream()) {
bf.Serialize(inms, info.GetValue(obj));
byte[] ba = inms.ToArray();
// for length
ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));
// for value
ms.Write(ba, 0, ba.Length);
}
}
return ms.ToArray();
}
}
Вышеупомянутая функция просто использует BinaryFormatter
для сериализации неизвестного необработанного размера object
, и я просто отслеживаю также размер и сохраняю его в выводе MemoryStream
.
public static void ByteArrayToStruct<T>(byte[] data, out T output)
{
output = (T) Activator.CreateInstance(typeof(T), null);
using (MemoryStream ms = new MemoryStream(data))
{
byte[] ba = null;
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
// for length
ba = new byte[sizeof(int)];
ms.Read(ba, 0, sizeof(int));
// for value
int sz = BitConverter.ToInt32(ba, 0);
ba = new byte[sz];
ms.Read(ba, 0, sz);
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream(ba))
{
info.SetValue(output, bf.Deserialize(inms));
}
}
}
}
Когда мы хотим преобразовать его обратно в исходный struct
, мы просто считываем длину обратно и напрямую сбрасываем его обратно в BinaryFormatter
, который, в свою очередь, сбрасывает его обратно в struct
.
Эти 2 функции являются общими и должны работать с любым struct
, я протестировал приведенный выше код в своем проекте C#
, где у меня есть сервер и клиент, подключен и общаюсь через NamedPipeStream
, и я пересылаю свой struct
как байтовый массив от одного к другому и преобразованный обратно.
Я полагаю, что мой подход мог бы быть лучше, поскольку он не фиксирует длину на самом struct
и единственные издержки - это просто int
для всех полей, которые есть в вашей структуре. Кроме того, внутри байтового массива, сгенерированного BinaryFormatter
, есть некоторые незначительные накладные расходы, но, кроме этого, это немного.