Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C#, how can I reinterpret byte[] as T[], where T is a struct?

I'm using C# (.NET 5). Imagine I have a class that stores an array of structures (say, floats):

public class StoresArray
{
    private float[] floats;
}

This class's data is loaded from a serialized binary file. To assign the floats array, I use a helper function to read bytes from the serialized file. Importantly, this function then attempts to reinterpret the loaded bytes directly as float[] rather than copying to a new array.

public static class Deserializer
{
    public static float[] Load(string file)
    {
        byte[] bytes = LoadBytesFromFile(file);

        // This is a compiler error, of course.
        return (float[])bytes;
    }
}

The intended usage is as follows:

// Within the StoresArray class...
floats = Deserializer.Load("MyFile.file");

Of note here is that I'm attempting to store the float[] as a member variable, not just iterate over the byte[] locally. As such, casting via Span<T> (Span<float> floatSpan = MemoryMarshal.Cast<byte, float>(bytes.AsSpan())) is insufficient. Functions associated with Memory<T>, Marshal, and MemoryMarshal have similarly failed as well. Of course I could use spans (along with other methods, like BitConverter or unsafe pointers) to build a new float[] from the byte[], but that would incur an additional array allocation, as well as additional operations to convert the bytes. In the context in which I'm asking (loading video game assets on the fly), I'd like to optimize performance as much as I can.

In modern C#, is it possible to reinterpret and store arrays of structs without incurring an additional allocation?

like image 597
Grimelios Avatar asked Dec 10 '21 19:12

Grimelios


Video Answer


1 Answers

To write you can do something like this:

public static void WriteArrayToStream<T>(Stream output, T[] array) where T: unmanaged
{
    var span = array.AsSpan();
    var bytes = MemoryMarshal.AsBytes(span);
    output.Write(bytes);
}

For reading, you can do something like this:

public static (T[] Result, int Count) ReadArrayFromStream<T>(Stream input, int n) where T: unmanaged
{
    T[] result = new T[n];
    var span   = result.AsSpan();
    var bytes  = MemoryMarshal.AsBytes(span);
    int count  = input.Read(bytes);

    return (result, count/Marshal.SizeOf<T>());
}

Note that it returns a tuple, because if not enough data is available, only the first Count elements will have valid data.

Here's an example to show both writing and reading a double[] array:

MemoryStream mem = new MemoryStream();
double[] data = Enumerable.Range(0, 100).Select(x => (double)x).ToArray();
WriteArrayToStream(mem, data);
Console.WriteLine(mem.Length); // 800

mem.Position       = 0;
var (array, count) = ReadArrayFromStream<double>(mem, 200);
Console.WriteLine(count); // 100
Console.WriteLine(array[42]);  // 42
like image 176
Matthew Watson Avatar answered Oct 04 '22 16:10

Matthew Watson