【发布时间】:2020-11-19 06:23:54
【问题描述】:
梦想
我正在寻找一种方法将十进制值复制到字节数组缓冲区中,然后无需任何堆分配即可将这些字节读回十进制。理想情况下,这不需要不安全的上下文。
我的尝试
我以前在 C# 中使用过临时联合来做一些疯狂的事情。这是一种非常酷的方式,可以随意随意读取内存,但您必须小心。您可能会进入变量我明确存在的损坏状态,例如byte[],但在调试器中查看的值是int[]。我什至不知道这样的事情是可能的!
注意:Marc 在下面的 cmets 中提出了一个非常重要的观点。由于字节顺序,您无法使用像这样的重叠结构概念可靠地将数字直接转换为字节。在这种情况下,您可以安全地使用整数,因为十进制类型在内部使用了 4 个整数。这是来自 protobuf-net 的十进制序列化程序的 [示例]。
1: struct union w/decimal and byte[]
第一次尝试尝试使用带有 decimal 和 byte[] 字段的结构联合概念,两者都在 0 偏移处,因此它们占用完全相同的内存位置。然后我可以写入一个字段并从另一个字段读取。
[StructLayout(LayoutKind.Explicit)]
private readonly struct convert_decimal_to_bytes
{
[FieldOffset(0)]
public readonly decimal value;
[FieldOffset(0)]
public readonly byte[] bytes;
public convert_decimal_to_bytes(decimal value)
{
bytes = default;
this.value = value;
}
}
这甚至不会在没有抛出的情况下运行 - 类型无法加载以下内容:
System.TypeLoadException : Could not load type 'convert_decimal_to_bytes' 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 convert_decimal_to_bytes
{
[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 convert_decimal_to_bytes(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 convert_decimal_to_bytes(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 convert_decimal_to_bytes(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 convert_decimal_to_bytes(startIndex, buffer);
return convert.value;
}
}
【问题讨论】:
-
字节数组肯定需要分配。您可能的意思是该方法应该将
decimals 内容传输到外部提供的缓冲区,或者? decimal.GetBits(Decimal, Span<Int32>) 是否符合您的需求? -
当然缓冲区需要分配。我不认为这是模棱两可的。缓冲区总是在方法之外。我认为这不值得投反对票。我将证明我正在寻找的样本。 decimal.GetBits 分配 int[4] - 所以不,它显然不符合我的需要。 ——为什么投反对票?我添加了一个示例方法签名,因此希望这可以消除歧义。
-
@mhand:您提到了返回
int[]的decimal.GetBits方法,但 Klaus 明确 提到了接受Span<int>的重载,而 确实如此 满足您的需求(如我的回答所示)。在拒绝那些试图帮助你的人的建议之前,请小心。 -
我还强烈建议即使对于一次性示例代码也遵循正常的 C# 和 .NET 命名约定 - 所有那些非常规的下划线(以及以小写字母开头的类型名称)都可能会让人分心。
-
您可能对这篇文章感兴趣:Fun with
__makeref。滚动到标题为“用例:重新解释变量”的部分。
标签: c#