在 C# 中,如何将 byte[] 重新解释为 T[],其中 T 是一个结构?
In C#, how can I reinterpret byte[] as T[], where T is a struct?
我正在使用 C# (.NET 5)。假设我有一个 class 存储结构数组(例如,浮点数):
public class StoresArray
{
private float[] floats;
}
此 class 的数据是从序列化二进制文件加载的。为了分配 floats
数组,我使用辅助函数从序列化文件中读取字节。重要的是,此函数然后尝试 重新解释 加载的字节 直接作为 float[]
而不是复制到新数组。
public static class Deserializer
{
public static float[] Load(string file)
{
byte[] bytes = LoadBytesFromFile(file);
// This is a compiler error, of course.
return (float[])bytes;
}
}
预期用途如下:
// Within the StoresArray class...
floats = Deserializer.Load("MyFile.file");
这里值得注意的是,我试图 将 float[]
存储为成员变量 ,而不仅仅是迭代在 byte[]
本地。因此,通过 Span<T>
(Span<float> floatSpan = MemoryMarshal.Cast<byte, float>(bytes.AsSpan())
) 进行转换是不够的。与 Memory<T>
、Marshal
和 MemoryMarshal
关联的函数也同样失败。当然,我可以使用跨度(连同其他方法,如 BitConverter
或不安全指针)从 byte[]
构建一个 new float[]
,但是这将导致额外的数组分配,以及转换字节的额外操作。在我询问的上下文中(动态加载视频游戏资产),我想尽可能地优化性能。
在现代 C# 中,是否可以重新解释和存储 结构数组而不产生额外的分配?
要写作你可以这样做:
public static void WriteArrayToStream<T>(Stream output, T[] array) where T: unmanaged
{
var span = array.AsSpan();
var bytes = MemoryMarshal.AsBytes(span);
output.Write(bytes);
}
为了阅读,你可以这样做:
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>());
}
请注意,它 returns 是一个元组,因为如果没有足够的数据可用,只有前 Count
个元素会有有效数据。
下面是一个显示写入和读取 double[]
数组的示例:
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
我正在使用 C# (.NET 5)。假设我有一个 class 存储结构数组(例如,浮点数):
public class StoresArray
{
private float[] floats;
}
此 class 的数据是从序列化二进制文件加载的。为了分配 floats
数组,我使用辅助函数从序列化文件中读取字节。重要的是,此函数然后尝试 重新解释 加载的字节 直接作为 float[]
而不是复制到新数组。
public static class Deserializer
{
public static float[] Load(string file)
{
byte[] bytes = LoadBytesFromFile(file);
// This is a compiler error, of course.
return (float[])bytes;
}
}
预期用途如下:
// Within the StoresArray class...
floats = Deserializer.Load("MyFile.file");
这里值得注意的是,我试图 将 float[]
存储为成员变量 ,而不仅仅是迭代在 byte[]
本地。因此,通过 Span<T>
(Span<float> floatSpan = MemoryMarshal.Cast<byte, float>(bytes.AsSpan())
) 进行转换是不够的。与 Memory<T>
、Marshal
和 MemoryMarshal
关联的函数也同样失败。当然,我可以使用跨度(连同其他方法,如 BitConverter
或不安全指针)从 byte[]
构建一个 new float[]
,但是这将导致额外的数组分配,以及转换字节的额外操作。在我询问的上下文中(动态加载视频游戏资产),我想尽可能地优化性能。
在现代 C# 中,是否可以重新解释和存储 结构数组而不产生额外的分配?
要写作你可以这样做:
public static void WriteArrayToStream<T>(Stream output, T[] array) where T: unmanaged
{
var span = array.AsSpan();
var bytes = MemoryMarshal.AsBytes(span);
output.Write(bytes);
}
为了阅读,你可以这样做:
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>());
}
请注意,它 returns 是一个元组,因为如果没有足够的数据可用,只有前 Count
个元素会有有效数据。
下面是一个显示写入和读取 double[]
数组的示例:
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