7

In the example C# 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#?

JamesPD
  • 243
  • 1
  • 3
  • 7

7 Answers7

15

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.

Christopher Currens
  • 29,917
  • 5
  • 57
  • 77
  • +1, Good answer, how do these ways compare to using LayoutKind.Explicit as in my example? (or is there a reason my way won't work?) – Hogan Aug 11 '11 at 17:48
  • 1
    Yours won't compile, because `byte[any number] varName` isn't valid C# code. Also, StructLayout is meant to let the marshaller know the order and/or offsets of the fields in the structure. I noticed you've specified the same offset for two values, and I think the marshaller will throw an error for that. If you want to use Attributes to specify array sizes, you can use the `MarshalAs` attribute and have it marshal as an `UnmangedType.ByValArray` with `SizeConst` as the array size. – Christopher Currens Aug 11 '11 at 18:00
  • Same offset is valid, see microsoft documentation here: http://msdn.microsoft.com/en-us/library/aa288471(v=vs.71).aspx – Hogan Aug 11 '11 at 19:01
  • @Hogan, You're right. That's what I get for going from memory :) Either way, the important thing, is that using the `MarshalAs` attribute would be better than explicitly specifying the layout in this case. Specifying the layout won't allow the marshaller to account for the array size, unless you're using unsafe code. – Christopher Currens Aug 11 '11 at 20:34
  • I like this `unsafe struct ExampleClass` idea. This works but I need to convert it back to bytes. Is there similar way to do that? – MrHIDEn Feb 27 '15 at 10:16
  • `fixed (byte* pb = &data[0])` == `fixed (byte* pb = data)` – MrHIDEn Feb 27 '15 at 10:18
  • "because structs are immutable" - no, that's a (debatable) usage recommendation only. – RenniePet Apr 29 '15 at 00:56
  • @RenniePet You're right, it's not an accurate statement. I wasn't intending to say that they are immutable (obviously public fields are not immutable), I was poorly trying to say that they are passed by value and thus copied (as the latter half of the sentence indicates). Thanks for the comment. I'll update the wording. – Christopher Currens Apr 29 '15 at 15:21
  • Thanks again for this answer - it made me aware of how unsafe code could be used to map a struct to a byte array. I've now taken the concept one step further, because I wanted to both read _and write_ the mapped fields in the byte array. See the "answer" I've posted below. – RenniePet May 01 '15 at 03:46
3

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
}
Hogan
  • 69,564
  • 10
  • 76
  • 117
  • It's important to know that the fixed keyword can only be used in an unsafe context. Fixed literally means that the field is actually of type `byte*` instead of a byte[]. – Christopher Currens Aug 11 '11 at 20:35
  • @Christopher - this is correct, it might even need an unsafe modifier for the stuct decl. I did not try and compile this code. – Hogan Aug 12 '11 at 02:20
3

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.

vcsjones
  • 138,677
  • 31
  • 291
  • 286
  • Neat --- so to copy the data in you copy to handle before you free it? (can you give an example of that?) – Hogan Aug 11 '11 at 17:35
  • 1
    @Hogan: The purpose of the GCHandle is to pin the byte[] so that the CLR / GC doesn't move it while `Marshal.PtrToStructure` is operating, and also so we can get the address where the byte[] lives. The `Free` is for the GCHandle itself. `Alloc` doesn't copy the byte[] contents. – vcsjones Aug 11 '11 at 20:27
  • 1
    I think you mean "typeof (ExampleStruct)". – RenniePet Apr 28 '15 at 23:27
1

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 });
      }
   }
}
RenniePet
  • 11,420
  • 7
  • 80
  • 106
1

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)
CommonSense
  • 341
  • 1
  • 6
  • 4
    "make your code maintainable and readable first" - well, you've demonstrated exactly why your suggested solution is not very good. It's not readable (it contains lots of "magic numbers"), and it's definitely not maintainable. If I wanted to add a new field between field1 and field2 there's a 90% chance that I'd do it wrong. – RenniePet Apr 28 '15 at 23:13
0

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

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

    }
}
Dinsdale
  • 581
  • 7
  • 12