我在 C# 中有这段代码,我需要以最快的方式将字节数组复制到结构数组。
我有这样的结构:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe public struct TsVarGroup
{
public uint Addr;
public ushort Count;
public short CAddr;
public ushort CSW;
public fixed byte NamePrefix[9];
public string GetNamePrefix()
{
fixed (byte* ptr = NamePrefix)
{
return new string((sbyte*)ptr, 0, 8, Encoding.ASCII);
}
}
}
asVarGroups = new TsVarGroup[sVarTable.Count];
byte[] dataVarGroups = Connection.GETMEM("R", (sVarTable.Addr - ADDRESS_OFFSET), asVarGroups.Length * TS_VARGROUP_SIZE);
有没有简单的方法可以将字节数组
dataVarGroups
复制到结构数组asVarGroups
?
我试过这个:
Buffer.BlockCopy(dataVarGroups, 0, asVarGroups, 0, dataVarGroups.Length);
但它仅适用于基元元素。
您可以使用
MemoryMarshal.Cast
将一种 blittable 类型的跨度强制转换为另一种类型;数组可以被视为跨度,并且字节和您的类型似乎都是 blittable,所以:
byte[] bytes = ...
TsVarGroup[] target = ...
MemoryMarshal.Cast<byte, TsVarGroup>(bytes).CopyTo(target);
您可以这样做以在任何方向上强制 blittable 类型。生成的跨度将根据被双关的两种类型各自的大小正确调整大小 - 即,如果您将 100 字节的跨度强制为
int
,它将具有长度 25。
这与强制非托管指针类似,但不需要固定或
unsafe
代码(尽管显然:您仍然需要小心)。
最常见的是,这种强制转换用于避免需要第二个数组,从而允许您处理一种数组类型就像它是另一种数组(通过跨度)。
如果您使用的是 C# 12 或更高版本,则无需使用任何
unsafe
代码即可通过使用新的 System.Runtime.CompilerServices.InlineArray
属性来定义 9 字节的固定数组来执行此操作,如下所示:
[System.Runtime.CompilerServices.InlineArray(9)]
public struct NineBytes
{
byte _element0;
public static NineBytes Create(IReadOnlyList<byte> bytes)
{
var result = new NineBytes();
for (int i = 0; i < 9; ++i)
result[i] = bytes[i];
return result;
}
}
我为下面的演示程序添加了一个
Create()
辅助方法到该结构;它不是结构的必要部分。
一旦你有了它,你就可以像这样声明你的结构:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TsVarGroup
{
public uint Addr;
public ushort Count;
public short CAddr;
public ushort CSW;
public NineBytes NamePrefix;
public string GetNamePrefix()
{
return Encoding.ASCII.GetString(NamePrefix);
}
}
然后您可以使用
MemoryMarshal.Cast<>
将字节数组转换(不复制数据)到 Span<TsVarGroup>
或 ReadOnlySpan<TsVarGroup>
:
Span<TsVarGroup> fromBytes = MemoryMarshal.Cast<byte, TsVarGroup>(asBytes);
将所有内容放在一个简单的控制台测试应用程序中:
using System.Runtime.InteropServices;
using System.Text;
namespace Net8Console;
public static class Program
{
public static void Main()
{
// Create array of structs:
TsVarGroup[] original = [
new TsVarGroup
{
Addr = 1,
Count = 2,
CAddr = 3,
CSW = 4,
NamePrefix = createNineAsciiBytes('A')
},
new TsVarGroup
{
Addr = 2,
Count = 3,
CAddr = 4,
CSW = 5,
NamePrefix = createNineAsciiBytes('B')
},
new TsVarGroup
{
Addr = 3,
Count = 4,
CAddr = 5,
CSW = 6,
NamePrefix = createNineAsciiBytes('C')
}
];
byte[] asBytes = MemoryMarshal.AsBytes(original.AsSpan()).ToArray();
Span<TsVarGroup> fromBytes = MemoryMarshal.Cast<byte, TsVarGroup>(asBytes);
// No copying of the 'asBytes' array occurs when creating 'fromBytes'.
for (int i = 0; i < fromBytes.Length; i++)
{
Console.WriteLine(fromBytes[i].GetNamePrefix());
}
}
static NineBytes createNineAsciiBytes(char firstChar)
{
return NineBytes.Create(nineAsciiChars((byte)firstChar).ToList());
}
static IEnumerable<byte> nineAsciiChars(byte firstChar)
{
for (byte ch = firstChar, lastChar = (byte)(ch + 9); ch != lastChar; ++ch)
yield return ch;
}
}
[System.Runtime.CompilerServices.InlineArray(9)]
public struct NineBytes
{
byte _element0;
public static NineBytes Create(IReadOnlyList<byte> bytes)
{
var result = new NineBytes();
for (int i = 0; i < 9; ++i)
result[i] = bytes[i];
return result;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TsVarGroup
{
public uint Addr;
public ushort Count;
public short CAddr;
public ushort CSW;
public NineBytes NamePrefix;
public string GetNamePrefix()
{
return Encoding.ASCII.GetString(NamePrefix);
}
}
如果你运行它,它将输出:
ABCDEFGHI
BCDEFGHIJ
CDEFGHIJK
这表明数据已从原始数据正确转换。