将字节数组复制到C#中的类/结构中的各个字段

在下面的示例C#代码中,我有一个从套接字读取的字节数组。 我想将数据解析为’exampleClass’的各个字段(前8个字节到64位变量’field1’,接下来4个字节到32位变量’field2’等)

using System; namespace CsByteCopy { class Program { class ExampleClass { public UInt64 field1; public UInt32 field2; public UInt16 field3; public byte[] field4 = new byte[18]; } static void Main(string[] args) { byte[] exampleData = { // These 8 bytes should go in 'field1' 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, // These 4 bytes should go in 'field2' 0x08,0x09,0x0A,0x0B, // These 2 bytes should go in 'field3' 0x0C,0x0D, // These 18 * 1 bytes should go in 'field4' 0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, }; ExampleClass exampleClass = new ExampleClass(); // Perform copy } } } 

自从我上次使用C以来已经很长时间了,但是如果我没记错的话,我可能已经能够通过单个memcpy()调用来填充类中的所有字段。 在C#中填充’exampleClass’字段的最有效方法是什么?

你有很多选择,最好的选择通常取决于你的程序需要/可以处理什么以及你想要多少速度。 有很多文章解释了使用数据填充类或结构的不同方法。

二进制序列化是一种方法。 这需要您编写自定义序列化程序,这非常简单。 有关这方面的MSDN文章显示它并不太难。

您可以编写一个私有构造函数,它接受字节数组并使用BinaryReader或BitConverter类从字节数组中读取(您必须将其包装在MemoryStream用于BinaryReader )或简单地将字节数组的部分转换为你需要的值( BitConverter )。 在BitConverter的情况下,您还需要使用Buffer.BlockCopy将字节数组的剩余数据复制到类中的字节数组字段

第三种方法,通常也是最快的方法,是将您的类转换为结构并使用不安全的代码将字节数组转换为该结构。 像这样的东西:

 unsafe struct ExampleClass { public ulong field1; public uint field2 public ushort field3 public fixed byte field4[18]; public static ExampleClass ReadStruct(byte[] data) { fixed (byte* pb = &data[0]) { return *(ExampleClass*)pb; } } } 

当然,上述代码仅在您使用不安全代码时才有效。 此外,将类转换为结构也可能不是您正在寻找的。 在大多数情况下,结构体通过值传递给函数,因此调用方法复制整个结构(在这种情况下为32个字节)而不是传递引用(4或8个字节,具体取决于CPU体系结构),并且可能降低您的效率程序。 结构通过值传递有例外,您可以使用自己喜欢的搜索引擎。

如果你不能使用不安全的代码,那么还有Marshal.PtrToStructure ,它将与上面的代码一样,但大约慢10倍。 您还需要使用MarshalAs属性来指定数组的大小,而不是使用fixed关键字(这是不安全的)。 此时,您可以使用BinaryReader / BitConverter,因为它将比编组类更快。

我想你想要这个:

这里通过更改field4中的缓冲区,您还可以更改其他3个特定类型。

 [StructLayout(LayoutKind.Explicit, Size=8)] struct UValue { [FieldOffset(0)] public fixed byte field4[18]; // 18 bytes long [FieldOffset(0)] public uint64 field1; [FieldOffset(8)] public Uint32 field2 [FieldOffset(12)] public Uint16 field3 } 

你可能想要这个:

这里通过更改fieldload中的缓冲区,您可以像上面那样更改其他缓冲区,但最后还有另一个字节缓冲区。

 [StructLayout(LayoutKind.Explicit, Size=8)] struct UValue { [FieldOffset(0)] public fixed byte fieldload[38]; // modify this to modify others [FieldOffset(0)] public uint64 field1; [FieldOffset(8)] public Uint32 field2 [FieldOffset(12)] public Uint16 field3 [FieldOffset(14)] public fixed byte field4[18]; // 18 bytes long } 

如果可以使用结构,另一个选项是将字节数组直接编组到结构中。

 struct ExampleStruct { public UInt64 field1; public UInt32 field2; public UInt16 field3; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)] public byte[] field4; } 

并获得一个结构:

 var handle = GCHandle.Alloc(exampleData, GCHandleType.Pinned); var structure = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof (ExampleStruct)); handle.Free(); 

structure将包含您的数据。

忘记效率的事情,让你的代码首先可维护和可读。 如有必要,进行剖析和改进。

使用BitConverter类有什么问题。

 field1 = BitConverter.ToInt64(exampleData, 0) field2 = BitConverter.ToInt32(exampleData, 8) field3 = BitConverter.ToInt16(exampleData, 12) Array.Copy(exampleData, 14, field4, 0, 18) 

现在换一些完全不同的东西……

这并没有真正回答OP的问题; 相反,它是我做的事情,提供了一种在字节数组之上映射C#结构的方法,并允许读取和写入底层字节数组中的各个字段。 它使用不安全的代码,并获取指向字节数组的指针,然后将其转换为指向映射字段的结构的指针。

这可能不是那么有效,但具有提供字段的符号映射而不使用字段的长度和偏移的“幻数”的优点。 但请注意,这对于使用大端而不是小端数据表示的系统进行数据交换不起作用。

字段访问方法实现为扩展方法。 有关如何使用它们的示例,请参阅TestMethod()。

这是受到Christopher Currens的回答的启发 – 如果你觉得这很有用,请给他一个upvote。

 using System; using System.Runtime.InteropServices; namespace ConsoleApplication1 { [StructLayout(LayoutKind.Sequential, Pack = 1)] unsafe struct ExampleStruct { internal const int CField4Length = 18; public UInt64 Field1; public UInt32 Field2; public UInt16 Field3; public fixed byte Field4[CField4Length]; } static unsafe class ExampleStructExtensionMethods { public static UInt64 GetField1(this byte[] byteArray) { fixed (byte* byteArrayPointer = &byteArray[0]) { return (*(ExampleStruct*)byteArrayPointer).Field1; } } public static UInt32 GetField2(this byte[] byteArray) { fixed (byte* byteArrayPointer = &byteArray[0]) { return (*(ExampleStruct*)byteArrayPointer).Field2; } } public static UInt16 GetField3(this byte[] byteArray) { fixed (byte* byteArrayPointer = &byteArray[0]) { return (*(ExampleStruct*)byteArrayPointer).Field3; } } public static byte[] GetField4(this byte[] byteArray) { fixed (byte* byteArrayPointer = &byteArray[0]) { byte[] field4 = new byte[ExampleStruct.CField4Length]; for (int i = 0; i < ExampleStruct.CField4Length; i++) field4[i] = (*(ExampleStruct*)byteArrayPointer).Field4[i]; return field4; } } public static void SetField1(this byte[] byteArray, UInt64 field1) { fixed (byte* byteArrayPointer = &byteArray[0]) { (*(ExampleStruct*)byteArrayPointer).Field1 = field1; } } public static void SetField2(this byte[] byteArray, UInt32 field2) { fixed (byte* byteArrayPointer = &byteArray[0]) { (*(ExampleStruct*)byteArrayPointer).Field2 = field2; } } public static void SetField3(this byte[] byteArray, UInt16 field3) { fixed (byte* byteArrayPointer = &byteArray[0]) { (*(ExampleStruct*)byteArrayPointer).Field3 = field3; } } public static void SetField4(this byte[] byteArray, byte[] field4) { if (field4.Length != ExampleStruct.CField4Length) throw new ArgumentException("Byte array must have length 18", "field4"); fixed (byte* byteArrayPointer = &byteArray[0]) { for (int i = 0; i < ExampleStruct.CField4Length; i++) (*(ExampleStruct*)byteArrayPointer).Field4[i] = field4[i]; } } } class TestProgram { byte[] exampleData = { // These 8 bytes should go in 'field1' 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, // These 4 bytes should go in 'field2' 0x08,0x09,0x0A,0x0B, // These 2 bytes should go in 'field3' 0x0C,0x0D, // These 18 * 1 bytes should go in 'field4' 0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, }; public void TestMethod() { UInt64 field1 = exampleData.GetField1(); UInt32 field2 = exampleData.GetField2(); UInt16 field3 = exampleData.GetField3(); byte[] field4 = exampleData.GetField4(); exampleData.SetField1(++field1); exampleData.SetField2(++field2); exampleData.SetField3(++field3); exampleData.SetField4(new byte[ExampleStruct.CField4Length] { 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42 }); } } } 

要使用类而不是struct,可以使用类似下面的内容。 我使用了上述答案的各种元素,并将它们组合在一起以形成高性能课程。 如果您不喜欢Serialize / Deserialize方法的所有手动编码,您可以非常轻松地编写代码生成器,该代码生成器将遍历所有字段/属性并发出适当的方法。 这是代码:

 public interface ISerializableClass { int SerializableClassSize { get; } StreamInflateTest Deserialize(byte[] buffer, int startOffset); byte[] Serialize(byte[] buffer, int startOffset); } public class StreamInflateTest : ISerializableClass { private const int _classSize = 10; public float Float32Value { get; set; } public Int32 Int32Value { get; set; } public byte Byte8Value { get; set; } public bool IsOk0 { get; set; } public bool IsOk1 { get; set; } public bool IsOk2 { get; set; } public bool IsOk3 { get; set; } public bool IsOk4 { get; set; } public StreamInflateTest() { } public int SerializableClassSize { get { return _classSize; } } public StreamInflateTest(byte[] buffer, int startOffset) { Deserialize(buffer, startOffset); } public unsafe StreamInflateTest Deserialize(byte[] buffer, int startOffset) { fixed (byte* pb = &buffer[startOffset]) { Float32Value = *(float*)pb; Int32Value = *(int*)(pb + 4); Byte8Value = pb[8]; BitField8 bitfld = new BitField8(pb[9]); IsOk0 = bitfld.Bit0; IsOk1 = bitfld.Bit1; IsOk2 = bitfld.Bit2; IsOk3 = bitfld.Bit3; IsOk4 = bitfld.Bit4; } return this; } public unsafe byte[] Serialize(byte[] buffer, int startOffset) { fixed (byte* pb = &buffer[startOffset]) { *(float*)pb = Float32Value; *(int*)(pb + 4) = Int32Value; pb[8] = Byte8Value; BitField8 bitfld = new BitField8(0) { Bit0 = IsOk0, Bit1 = IsOk1, Bit2 = IsOk2, Bit3 = IsOk3, Bit4 = IsOk4 }; pb[9] = bitfld.Value; } return buffer; } } public struct BitField8 { public byte Value; public BitField8(byte value) { Value = value; } public bool Bit0 { get { return (Value & 0x01) != 0; } set { if (value) Value |= 0x01; else Value = (byte)(Value & 0xFE); // clear the bit } } public bool Bit1 { get { return (Value & 0x02) != 0; } set { if (value) Value |= 0x02; else Value = (byte)(Value & 0xFD); // clear the bit } } public bool Bit2 { get { return (Value & 0x04) != 0; } set { if (value) Value |= 0x04; else Value = (byte)(Value & 0xFB); // clear the bit } } public bool Bit3 { get { return (Value & 0x08) != 0; } set { if (value) Value |= 0x08; else Value = (byte)(Value & 0xF7); // clear the bit } } public bool Bit4 { get { return (Value & 0x10) != 0; } set { if (value) Value |= 0x10; else Value = (byte)(Value & 0xEF); // clear the bit } } public bool Bit5 { get { return (Value & 0x20) != 0; } set { if (value) Value |= 0x20; else Value = (byte)(Value & 0xDF); // clear the bit } } public bool Bit6 { get { return (Value & 0x40) != 0; } set { if (value) Value |= 0x40; else Value = (byte)(Value & 0xBF); // clear the bit } } public bool Bit7 { get { return (Value & 0x80) != 0; } set { if (value) Value |= 0x80; else Value = (byte)(Value & 0x7F); // clear the bit } } public bool Set(bool value, int bitNo) { if (bitNo > 7 || bitNo < 0) throw new ArgumentOutOfRangeException(); if (value) Value |= (byte)(0x01 << bitNo); else Value = (byte)(Value & ~(0x01 << bitNo)); // clear the bit return value; } public bool Get(int bitNo) { if (bitNo > 7 || bitNo < 0) throw new ArgumentOutOfRangeException(); return ((Value >> bitNo) & 0x01) != 0; } public bool this[int bitNo] { get { return Get(bitNo); } set { Set(value, bitNo); } } } 

这可用于编组字节数组并旋转字节顺序。 方便从C传递的网络消息。将您的结构包装在类中以通过ref传递它们。

 using System; using System.Runtime.InteropServices; using System.Reflection; using System.Net; namespace ConsoleApp1 { [StructLayout(LayoutKind.Sequential, Pack = 1)] struct MarshalMe { private UInt16 _param1; private UInt32 _param2; private UInt16 _param3; private UInt16 _param4; public ushort Param1 { get => _param1; } public uint Param2 { get => _param2; } public ushort Param3 { get => _param3; } public ushort Param4 { get => _param4; } } class Program { static void Main(string[] args) { byte[] bytes = new byte[] {0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00 }; var metoo = rotateStruct(stamp(bytes)); Console.WriteLine("{0}-{1}-{2}", metoo.Param1, metoo.Param2, metoo.Param3); Console.ReadKey(); } private static T stamp(byte[] bytes) { var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); var structure = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); return (T)structure; } private static T rotateStruct(object value) { FieldInfo[] myFieldInfo; Type myType = typeof(T); // Get the type and fields of FieldInfoClass. myFieldInfo = myType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); foreach (var s in myFieldInfo) { if (s.FieldType.Name == "UInt16"){ UInt16 u16 = (ushort)s.GetValue(value); u16 = (ushort)IPAddress.HostToNetworkOrder((short)u16); s.SetValue(value,u16 ); } else if(s.FieldType.Name == "UInt32") { UInt32 u32 = (uint)s.GetValue(value); u32 = (uint)IPAddress.HostToNetworkOrder((int)u32); s.SetValue(value, (object)u32); } else if (s.FieldType.Name == "UInt64") { UInt64 u64 = (ushort)s.GetValue(value); u64 = (ulong)IPAddress.HostToNetworkOrder((long)u64); s.SetValue(value, u64); } } return (T)value; } } }