0

Trying to create a function that converts objects to byte arrays (without the overhead/metadata things like BinaryFormatter create). I think I'm happy with the following code except the ability of it to convert UInt32[] arrays and Int32[] arrays to a byte array.

private byte[] ObjectToByteArray(object obj)
{
    string dataType = obj.GetType().Name;
    switch (dataType)
    {
        case "Byte[]": // an array of bytes
            return (byte[])obj;
        case "String": // a null-terminated ASCII string
            return Encoding.ASCII.GetBytes((string)obj);
        case "UInt16": // an array of unsigned short (16-bit) integers
            return BitConverter.GetBytes((ushort)obj);
        case "UInt32": // an array of unsigned long (32-bit) integers
            return BitConverter.GetBytes((uint)obj);
        case "UInt32[]": // an array of pairs of unsigned long (32-bit) integers
            //return BitConverter.GetBytes((ushort)obj);
            return null;
        case "Int32": // an array of signed long (32-bit) integers
            return BitConverter.GetBytes((int)obj);
        case "Int32[]": // an array of pairs of signed long (32-bit) integers
            //return BitConverter.GetBytes((int)obj);
            return null;
        default:
            throw new Exception($"The type of value ({dataType}) is not supported.");
    }
}

I was thinking of doing something like a simple for each 4 bytes loop and keep adding them to a byte array but was unsure if that would work or even be the best approach. I'm not even sure my current method is the best approach for what I have already made. Everything I have found on the web seems to confuse me and make my head spin when dealing with converting data types.

5
  • 1
    What's wrong with using BitConverter.GetBytes? Commented Nov 11, 2024 at 21:01
  • I'm not opposed to using that method, I was just not sure how to. Is there a reason to use it over the BlockCopy methods already submitted as answers? Commented Nov 11, 2024 at 21:27
  • You might consider returning a Span<T> or its readonly variant, since it allow you to cast the memory without any copying: MemoryMarshal.Cast<int, byte>(myArray.AsSpan()). But it does have some limitations, so it not always applicable. Commented Nov 12, 2024 at 7:49
  • Suggestion about your switch usage: don't switch on .GetType().Name, just switch on the variable itself and use actual types as cases. switch (obj) { case string str: ... case int integer: ... case int[] intArray: and so on. Commented Nov 12, 2024 at 8:28
  • Thanks for that @Orion! Is that more efficient? The reason I do it like I did in the example was it had a cleaner, easier to read look to me. But if there is a performance boost I'm all for it. Commented Nov 12, 2024 at 13:25

2 Answers 2

4

Just use Buffer.BlockCopy()

int[] intArray = { 1, 2, 3, 4 };
byte[] byteArray = new byte[intArray.Length * sizeof(int)];

Buffer.BlockCopy(intArray, 0, byteArray, 0, byteArray.Length);

Because Windows is little-endian (meaning least significant byte is first) you will get an array like this (when trying to print all the elements, space-separated):

1 0 0 0 2 0 0 0 3 0 0 0 4 0 0 0

An alternative solution, that will also get a byte array in little-endian fashion is by using spans:

int[] intArray = { 1, 2, 3, 4 };
byte[] byteArray = new byte[intArray.Length * sizeof(int)];

Span<int> intSpan = intArray;
Span<byte> byteSpan = byteArray;

MemoryMarshal.Cast<int, byte>(intSpan).CopyTo(byteSpan);

Console.WriteLine(string.Join(' ', byteSpan.ToArray()));
Sign up to request clarification or add additional context in comments.

3 Comments

Exactly this, it's so much faster than any method that accesses one element at a time.
Second statement is valid only for little-endian systems. On linux you will get 4-swap reverse
Great answer +1 from me, Thanks for the info!
3
    public static byte[] ToByteArray<T>(T[] obj) where T : unmanaged
    {
        var tgt = new byte[Buffer.ByteLength(obj)];
        Buffer.BlockCopy(obj, 0, tgt, 0, tgt.Length);
        return tgt;
    }

Mind Endianess of returned array. It is not safe to serialize it in LittleEndian (windows) and deserialize in BigEndian (linux/mac) and visa-versa. Nor its derivatives like hashes, etc. It's for in-memory use only.

This can be made stable if iterated manually using BinaryPrimitives. They default to BigEndian for both read/write on all systems.

3 Comments

I'm currently using this in conjunction with the PropertyItem.Value Property. I have a class wrapper that converts the Value to an object and was writing something to convert similar objects to a byte array when needed. I don't feel like Endianess would be a problem with me. It's all contained in one class and all being done in a Windows environment.
Why not use flatbuffers/zeroformatter? Its a lot faster than anything you do. Just copy - github.com/google/flatbuffers/tree/master/net/FlatBuffers or for custom DTOs you can use github.com/neuecc/ZeroFormatter which is basically zero-cost memory mapper.
I'll have to look into that for optimization purposes down the line. For now what you provided worked great thanks! Not really sure how to accept the answer here though. Both you and Rares' answer got me where I needed to go. I just liked yours because it was easy to turn into an extension method and uses as is.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.