梦想
我正在寻找一种将十进制值复制到字节数组缓冲区中,然后能够将这些字节读回十进制值而不需要任何堆分配的方法。理想情况下,这不需要使用不安全的上下文。
尝试
之前我在C#中使用过临时联合体来做一些疯狂的事情。这是一个非常酷的方式,可以随意以任何方式读取内存,但必须小心。您可能会陷入损坏状态,其中变量显式为byte[]
,但在调试器中查看的值为int[]
。我甚至不知道这种事情是可能的!
注意:Marc在下面的评论中提出了一个非常重要的观点。由于字节序问题,您不能可靠地使用类似于重叠结构概念的方式将数字直接转换为字节。在这种情况下,您可以安全地使用int,因为十进制类型在内部使用了4个int。这是protobuf-net十进制序列化器的一个[示例]。
1: 带有十进制和字节数组字段的结构联合
第一次尝试使用结构联合概念,其中包含一个decimal
和byte[]
字段,两者都在0偏移处,因此它们占用完全相同的内存位置。然后,我可以写入一个字段并从另一个字段读取。
[StructLayout(LayoutKind.Explicit)]
private readonly struct DecimalByteConverter
{
[FieldOffset(0)]
public readonly decimal value;
[FieldOffset(0)]
public readonly byte[] bytes;
public DecimalByteConverter(decimal value)
{
bytes = default;
this.value = value;
}
}
这个甚至无法运行,因为会抛出 -- 类型加载失败,具体如下:
System.TypeLoadException : Could not load type 'DecimalByteConverter' from assembly 'teloneum, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
CLR 显然不喜欢它们重叠 - 但仅因为一个是值类型,而另一个是引用类型。
2:带有十进制和16字节的结构联合!
所以我决定去掉引用类型!现在,以下内容“可以工作”,或者至少它将会工作,但排序被搞乱了。我肯定可以得到一个大的十进制数,通过这里运行它,然后只需使我的字段偏移量与我的测试匹配即可,但这似乎是错误的 - 而且,在我尝试这样做的每个十进制数中至少有三个零字节 :) 也许我最后变得懒了... 毕竟很晚了。
[StructLayout(LayoutKind.Explicit)]
private readonly struct DecimalByteConverter
{
[FieldOffset(0)] public readonly decimal value;
[FieldOffset( 0)] public readonly byte byte_1;
[FieldOffset( 1)] public readonly byte byte_2;
[FieldOffset( 2)] public readonly byte byte_3;
[FieldOffset( 3)] public readonly byte byte_4;
[FieldOffset( 4)] public readonly byte byte_5;
[FieldOffset( 5)] public readonly byte byte_6;
[FieldOffset( 6)] public readonly byte byte_7;
[FieldOffset( 7)] public readonly byte byte_8;
[FieldOffset( 8)] public readonly byte byte_9;
[FieldOffset( 9)] public readonly byte byte_10;
[FieldOffset(10)] public readonly byte byte_11;
[FieldOffset(11)] public readonly byte byte_12;
[FieldOffset(12)] public readonly byte byte_13;
[FieldOffset(13)] public readonly byte byte_14;
[FieldOffset(14)] public readonly byte byte_15;
[FieldOffset(15)] public readonly byte byte_16;
public DecimalByteConverter(decimal value)
{
byte_1 = default;
byte_2 = default;
byte_3 = default;
byte_4 = default;
byte_5 = default;
byte_6 = default;
byte_7 = default;
byte_8 = default;
byte_9 = default;
byte_10 = default;
byte_11 = default;
byte_12 = default;
byte_13 = default;
byte_14 = default;
byte_15 = default;
byte_16 = default;
this.value = value;
}
public DecimalByteConverter(int startIndex, byte[] buffer)
{
value = default;
byte_1 = buffer[startIndex++];
byte_2 = buffer[startIndex++];
byte_3 = buffer[startIndex++];
byte_4 = buffer[startIndex++];
byte_5 = buffer[startIndex++];
byte_6 = buffer[startIndex++];
byte_7 = buffer[startIndex++];
byte_8 = buffer[startIndex++];
byte_9 = buffer[startIndex++];
byte_10 = buffer[startIndex++];
byte_11 = buffer[startIndex++];
byte_12 = buffer[startIndex++];
byte_13 = buffer[startIndex++];
byte_14 = buffer[startIndex++];
byte_15 = buffer[startIndex++];
byte_16 = buffer[startIndex];
}
public static void Copy(decimal value, int startIndex, byte[] buffer)
{
var convert = new DecimalByteConverter(value);
buffer[startIndex++] = convert.byte_1;
buffer[startIndex++] = convert.byte_2;
buffer[startIndex++] = convert.byte_3;
buffer[startIndex++] = convert.byte_4;
buffer[startIndex++] = convert.byte_5;
buffer[startIndex++] = convert.byte_6;
buffer[startIndex++] = convert.byte_7;
buffer[startIndex++] = convert.byte_8;
buffer[startIndex++] = convert.byte_9;
buffer[startIndex++] = convert.byte_10;
buffer[startIndex++] = convert.byte_11;
buffer[startIndex++] = convert.byte_12;
buffer[startIndex++] = convert.byte_13;
buffer[startIndex++] = convert.byte_14;
buffer[startIndex++] = convert.byte_15;
buffer[startIndex] = convert.byte_16;
}
public static decimal Read(int startIndex, byte[] buffer)
{
var convert = new DecimalByteConverter(startIndex, buffer);
return convert.value;
}
}
int[]
的decimal.GetBits
方法,但 Klaus明确提到的是接受Span<int>
的重载版本,这完全符合您的需求(如我的回答所示)。在忽略那些试图帮助您的建议之前,请仔细考虑。 - Jon Skeet__makeref
。请滚动到标题为“用例:重新解释变量”的部分。 - John Wuint
值,而不是16个byte
值。在其他字节序的CPU上,工作方式会有很大不同。我在Jon的回答评论中链接了一个使用4个字段的示例。 - Marc Gravell