相当于Python的“struct.pack / unpack”的C#?

我是一位经验丰富的Python开发人员,并且已经开始喜欢它的许多便利。 我实际上已经知道C#已有一段时间了,但最近已经进入了一些更高级的编码。

我想知道的是,是否有办法将C#中的字节数组“解析”为一组(大小不同的)项目。

想象一下,我们有这个:

python:

import struct byteArray = "\xFF\xFF\x00\x00\x00\xFF\x01\x00\x00\x00" numbers = struct.unpack("<LHL",byteArray) print numbers[0] # 65535 print numbers[1] # 255 print numbers[2] # 1 newNumbers = [0, 255, 1023] byteArray = struct.pack("<HHL",newNumbers) print byteArray # '\x00\x00\xFF\x00\xFF\x03\x00\x00' 

我希望在C#中实现相同的效果,而不是像这样使用庞大,混乱的代码:

C#:

 byte[] byteArray = new byte[] { 255, 255, 0, 0, 0, 255, 1, 0, 0, 0 }; byte[] temp; int[] values = new int[3]; temp = new byte[4]; Array.Copy(byteArray, 0, temp, 0, 4); values[0] = BitConverter.ToInt32(temp); temp = new byte[2]; Array.Copy(byteArray, 4, temp, 0, 2); values[1] = BitConverter.ToInt16(temp); temp = new byte[4]; Array.Copy(byteArray, 8, temp, 0, 4); values[2] = BitConverter.ToInt32(temp); // Now values contains an array of integer values. // It would be OK to assume a common maximum (eg Int64) and just cast up to that, // but we still have to consider the size of the source bytes. // Now the other way. int[] values = new int[] { 0, 255, 1023 }; byteArray = new byte[8]; temp = BitConverter.GetBytes(values[0]); Array.Copy(temp,2,byteArray,0,2); temp = BitConverter.GetBytes(values[1]); Array.Copy(temp,2,byteArray,2,2); temp = BitConverter.GetBytes(values[2]); Array.Copy(temp,0,byteArray,4,4); 

显然,我所拥有的C#代码非常具体,并且不以任何方式真正可重用。

建议吗?

我最后编写了自己的类来处理这个问题。 它非常复杂,但似乎确实有效。 它也不完整,但它适用于我现在需要的东西。 随意使用它,如果有任何好的改进,请告诉我。

 using System; using System.Collections.Generic; using System.Linq; using System.Diagnostics; // This is a crude implementation of a format string based struct converter for C#. // This is probably not the best implementation, the fastest implementation, the most bug-proof implementation, or even the most functional implementation. // It's provided as-is for free. Enjoy. public class StructConverter { // We use this function to provide an easier way to type-agnostically call the GetBytes method of the BitConverter class. // This means we can have much cleaner code below. private static byte[] TypeAgnosticGetBytes(object o) { if (o is int) return BitConverter.GetBytes((int)o); if (o is uint) return BitConverter.GetBytes((uint)o); if (o is long) return BitConverter.GetBytes((long)o); if (o is ulong) return BitConverter.GetBytes((ulong)o); if (o is short) return BitConverter.GetBytes((short)o); if (o is ushort) return BitConverter.GetBytes((ushort)o); if (o is byte || o is sbyte) return new byte[] { (byte)o }; throw new ArgumentException("Unsupported object type found"); } private static string GetFormatSpecifierFor(object o) { if (o is int) return "i"; if (o is uint) return "I"; if (o is long) return "q"; if (o is ulong) return "Q"; if (o is short) return "h"; if (o is ushort) return "H"; if (o is byte) return "B"; if (o is sbyte) return "b"; throw new ArgumentException("Unsupported object type found"); } ///  /// Convert a byte array into an array of objects based on Python's "struct.unpack" protocol. ///  /// A "struct.pack"-compatible format string /// An array of bytes to convert to objects /// Array of objects. /// You are responsible for casting the objects in the array back to their proper types. public static object[] Unpack(string fmt, byte[] bytes) { Debug.WriteLine("Format string is length {0}, {1} bytes provided.", fmt.Length, bytes.Length); // First we parse the format string to make sure it's proper. if (fmt.Length < 1) throw new ArgumentException("Format string cannot be empty."); bool endianFlip = false; if (fmt.Substring(0, 1) == "<") { Debug.WriteLine(" Endian marker found: little endian"); // Little endian. // Do we need to flip endianness? if (BitConverter.IsLittleEndian == false) endianFlip = true; fmt = fmt.Substring(1); } else if (fmt.Substring(0, 1) == ">") { Debug.WriteLine(" Endian marker found: big endian"); // Big endian. // Do we need to flip endianness? if (BitConverter.IsLittleEndian == true) endianFlip = true; fmt = fmt.Substring(1); } // Now, we find out how long the byte array needs to be int totalByteLength = 0; foreach (char c in fmt.ToCharArray()) { Debug.WriteLine(" Format character found: {0}", c); switch (c) { case 'q': case 'Q': totalByteLength += 8; break; case 'i': case 'I': totalByteLength += 4; break; case 'h': case 'H': totalByteLength += 2; break; case 'b': case 'B': case 'x': totalByteLength += 1; break; default: throw new ArgumentException("Invalid character found in format string."); } } Debug.WriteLine("Endianness will {0}be flipped.", (object) (endianFlip == true ? "" : "NOT ")); Debug.WriteLine("The byte array is expected to be {0} bytes long.", totalByteLength); // Test the byte array length to see if it contains as many bytes as is needed for the string. if (bytes.Length != totalByteLength) throw new ArgumentException("The number of bytes provided does not match the total length of the format string."); // Ok, we can go ahead and start parsing bytes! int byteArrayPosition = 0; List outputList = new List(); byte[] buf; Debug.WriteLine("Processing byte array..."); foreach (char c in fmt.ToCharArray()) { switch (c) { case 'q': outputList.Add((object)(long)BitConverter.ToInt64(bytes,byteArrayPosition)); byteArrayPosition+=8; Debug.WriteLine(" Added signed 64-bit integer."); break; case 'Q': outputList.Add((object)(ulong)BitConverter.ToUInt64(bytes,byteArrayPosition)); byteArrayPosition+=8; Debug.WriteLine(" Added unsigned 64-bit integer."); break; case 'l': outputList.Add((object)(int)BitConverter.ToInt32(bytes, byteArrayPosition)); byteArrayPosition+=4; Debug.WriteLine(" Added signed 32-bit integer."); break; case 'L': outputList.Add((object)(uint)BitConverter.ToUInt32(bytes, byteArrayPosition)); byteArrayPosition+=4; Debug.WriteLine(" Added unsignedsigned 32-bit integer."); break; case 'h': outputList.Add((object)(short)BitConverter.ToInt16(bytes, byteArrayPosition)); byteArrayPosition += 2; Debug.WriteLine(" Added signed 16-bit integer."); break; case 'H': outputList.Add((object)(ushort)BitConverter.ToUInt16(bytes, byteArrayPosition)); byteArrayPosition += 2; Debug.WriteLine(" Added unsigned 16-bit integer."); break; case 'b': buf = new byte[1]; Array.Copy(bytes,byteArrayPosition,buf,0,1); outputList.Add((object)(sbyte)buf[0]); byteArrayPosition++; Debug.WriteLine(" Added signed byte"); break; case 'B': buf = new byte[1]; Array.Copy(bytes, byteArrayPosition, buf, 0, 1); outputList.Add((object)(byte)buf[0]); byteArrayPosition++; Debug.WriteLine(" Added unsigned byte"); break; case 'x': byteArrayPosition++; Debug.WriteLine(" Ignoring a byte"); break; default: throw new ArgumentException("You should not be here."); } } return outputList.ToArray(); } ///  /// Convert an array of objects to a byte array, along with a string that can be used with Unpack. ///  /// An object array of items to convert /// Set to False if you want to use big endian output. /// Variable to place an 'Unpack'-compatible format string into. /// A Byte array containing the objects provided in binary format. public static byte[] Pack(object[] items, bool LittleEndian, out string NeededFormatStringToRecover) { // make a byte list to hold the bytes of output List outputBytes = new List(); // should we be flipping bits for proper endinanness? bool endianFlip = (LittleEndian != BitConverter.IsLittleEndian); // start working on the output string string outString = (LittleEndian == false ? ">" : "<"); // convert each item in the objects to the representative bytes foreach (object o in items) { byte[] theseBytes = TypeAgnosticGetBytes(o); if (endianFlip == true) theseBytes = (byte[])theseBytes.Reverse(); outString += GetFormatSpecifierFor(o); outputBytes.AddRange(theseBytes); } NeededFormatStringToRecover = outString; return outputBytes.ToArray(); } public static byte[] Pack(object[] items) { string dummy = ""; return Pack(items, true, out dummy); } } 

.NET(以及C#)具有Marshal.StructureToPtrMarshal.PtrToStructure方法。

您可以滥用这些来将原始内存转换为struct就像在C中一样,而不是我建议这样做(因为它不是完全可移植的)。 您还需要将Byte[]数组缓冲区放入本机堆中,以便对其执行操作:

 T FromBuffer(Byte[] buffer) where T : struct { T temp = new T(); int size = Marshal.SizeOf(temp); IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.Copy(buffer, 0, ptr, size); T ret = (T)Marshal.PtrToStructure(ptr, temp.GetType()); Marshal.FreeHGlobal(ptr); return ret; } 

BinaryWriter和BinaryReader将任意项发送到字节数组或从字节数组中读取任意项

 var str = new MemoryStream(); var bw = new BinaryWriter(str); bw.Write(42); bw.Write("hello"); ... var bytes = str.ToArray();