开发者

Copying byte array to various fields in class/struct in C#

开发者 https://www.devze.com 2023-03-27 17:14 出处:网络
In the example 开发者_如何学CC# code below, I have a byte array which has been read from a socket. I want to parse the data into the various fields of \'exampleClass\' (first 8 bytes into the 64-bit v

In the example 开发者_如何学CC# code below, I have a byte array which has been read from a socket. I want to parse the data into the various fields of 'exampleClass' (first 8 bytes into the 64-bit variable 'field1', next 4 bytes into 32-bit variable 'field2', etc.)

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
    }
  }
}

It's been a long time since I last used C, but if I recall correctly, I might have been able to get away with a single memcpy() call to populate all the fields in the class. What's the most efficient way of populating the fields of 'exampleClass' in C#?


You have a lot of options, the one that turns out the best usually depends on what your program needs/can handle and exactly how much speed you want. There are a lot of articles that explain the different ways you can populate a class or a struct with data.

Binary Serialization is one way. That requires you write a custom serializer, which is fairly easy. An MSDN article regarding that shows it's not too difficult.

You can write a private constructor that takes in the byte array and uses a BinaryReader or the BitConverter class to read from the byte array (you'd have to wrap it in a MemoryStream for BinaryReader) or to simply convert sections of the byte array to the values you need (BitConverter). In the case of BitConverter you would also need to use Buffer.BlockCopy to copy the remaining data of the byte array to the byte array field in your class

A third way, also generally the fastest way, is to convert your class to a struct and use unsafe code to cast the byte array as that struct. Something like this:

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;
       }
   }
}

Of course, the above code is only valid if you can use unsafe code. Furthermore, converting the class to a struct may also not be what you're looking for. In most cases, structs are passed by value to functions, so that calling methods copies the entire struct (in this case 32 bytes) instead of passing a reference (4 or 8 bytes, depending on CPU architecture) and can reduce the efficiency of your program. There are exceptions to structs being passed by value, and you can use your favorite search engine for that.

If you can't use unsafe code, there is also Marshal.PtrToStructure which will do the same as the above code, but about 10 times slower. You would also need to use the MarshalAs attribute to specify the size of the array, instead of using the fixed keyword (which is unsafe). At that point, you might as well use the BinaryReader/BitConverter, as it will be faster than the marshal class.


I think you want this:

Here by changing the buffer in field4 you also change the other 3 which are specific types.

[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

}

You might want this:

Here by changing the buffer in fieldload you change the others as above but there is also another byte buffer at the end.

[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
}


Another option, if you can use a struct, is to Marshal the byte array directly into the structure.

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

And to get a struct for that:

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

And structure will contain your data.


Forget the efficiency thing and make your code maintainable and readable first. If necessary, profile and improve.

Whats wrong with using the BitConverter class.

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


And now for something completely different ...

This does not really answer the OP's question; instead it is something I cooked up to provide a way of mapping a C# struct on top of a byte array, and allow the individual fields in the underlying byte array to be both read and written. It uses unsafe code, and gets a pointer to the byte array and then casts it to a pointer to the struct that maps the fields.

This may not be all that efficient, but has the advantage of providing a symbolic mapping of the fields without the use of "magic numbers" for the lengths and offsets of the fields. But note that this will not work for data interchange with a system that uses big endian instead of little endian data representation.

The field access methods are implemented as extension methods. See TestMethod() for an example of how to use them.

This was inspired by the answer by Christopher Currens - if you find this useful please give him an 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 });
      }
   }
}


To use classes instead of struct, you can use something like the following. I used various elements of the above answers and put it together for a high performance class. If you don't like all the manual coding of the Serialize/Deserialize methods, you could quite easily write a code generator that would iterate through all the fields/properties and emit the appropriate methods. Here is the code:

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); }
    }
}


This can be used to marshal a byte array and rotate the byte order. Handy for network messages passed up from C. Wrap your structs in a class to pass them by 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<MarshalMe>(stamp<MarshalMe>(bytes));

            Console.WriteLine("{0}-{1}-{2}", metoo.Param1, metoo.Param2, metoo.Param3);

            Console.ReadKey();
        }

        private static T stamp<T>(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<T>(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;
        }      

    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号