Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - Cast a byte array to an array of struct and vice-versa (reverse)

I would like to save a Color[] to a file. To do so, I found that saving a byte array to a file using "System.IO.File.WriteAllBytes" should be very efficient.

I would like to cast my Color[] (array of struct) to a byte array into a safe way considering:

  • Potential problem of little endian / big endian (I think it is impossible to happen but want to be sure)
  • Having 2 differents pointer to the same memory which point to different type. Does the garbage collection will know what to do - moving objects - deleting a pointer ???

If it is possible, it would be nice to have a generic way to cast array of byte to array of any struct (T struct) and vice-versa.

If not possible, why ?

Thanks, Eric

I think that those 2 solutions make a copy that I would like to avoid and also they both uses Marshal.PtrToStructure which is specific to structure and not to array of structure:

  • Reading a C/C++ data structure in C# from a byte array
  • How to convert a structure to a byte array in C#?
like image 856
Eric Ouellet Avatar asked Dec 26 '22 23:12

Eric Ouellet


2 Answers

Since .NET Core 2.1, yes we can! Enter MemoryMarshal.

We will treat our Color[] as a ReadOnlySpan<Color>. We reinterpret that as a ReadOnlySpan<byte>. Finally, since WriteAllBytes has no span-based overload, we use a FileStream to write the span to disk.

var byteSpan = MemoryMarshal.AsBytes(colorArray.AsSpan());
fileStream.Write(byteSpan);

As an interesting side note, you can also experiment with the [StructLayout(LayoutKind.Explicit)] as an attribute on your fields. It allows you to specify overlapping fields, effectively allowing the concept of a union.

Here is a blog post on MSDN that illustrates this. It shows the following code:

[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
    [FieldOffset(0)]
    public UInt16 myInt;

    [FieldOffset(0)]
    public Byte byte1;

    [FieldOffset(1)]
    public Byte byte2;
}

In this example, the UInt16 field overlaps with the two Byte fields.

This seems to be strongly related to what you are trying to do. It gets you very close, except for the part of writing all the bytes (especially of multiple Color objects) efficiently. :)

like image 118
Timo Avatar answered Dec 29 '22 11:12

Timo


Regarding Array Type Conversion

C# as a language intentionally makes the process of flattening objects or arrays into byte arrays difficult because this approach goes against the principals of .NET strong typing. The conventional alternatives include several serialization tools which are generally seen a safer and more robust, or manual serialization coding such as BinaryWriter.

Having two variables of different types point to the same object in memory can only be performed if the types of the variables can be cast, implicitly or explicitly. Casting from an array of one element type to another is no trivial task: it would have to convert the internal members that keep track of things such as array length, etc.

A simple way to write and read Color[] to file

void WriteColorsToFile(string path, Color[] colors)
{
    BinaryWriter writer = new BinaryWriter(File.OpenWrite(path));

    writer.Write(colors.Length);

    foreach(Color color in colors)
    {
        writer.Write(color.ToArgb());
    }

    writer.Close();
}

Color[] ReadColorsFromFile(string path)
{
    BinaryReader reader = new BinaryReader(File.OpenRead(path));

    int length = reader.ReadInt32();

    Colors[] result = new Colors[length];

    for(int n=0; n<length; n++)
    {
        result[n] = Color.FromArgb(reader.ReadInt32());
    }

    reader.Close();
}
like image 30
nicholas Avatar answered Dec 29 '22 10:12

nicholas