C# *(decimal*)d=XXXm将导致除BinaryWriter.Write(XXXm)之外的另一个输出
我正在为自己的学习目的编写一个优化的二进制读写器。一切都很好,直到我为C# *(decimal*)d=XXXm将导致除BinaryWriter.Write(XXXm)之外的另一个输出,c#,decimal,unsafe,binarywriter,C#,Decimal,Unsafe,Binarywriter,我正在为自己的学习目的编写一个优化的二进制读写器。一切都很好,直到我为decimals的en-和解码编写了测试。我的测试还包括.NET Framework的BinaryWriter是否生成与我的BinaryWriter兼容的输出,反之亦然 我主要使用不安全和指针将变量写入字节数组。这些是通过指针和BinaryWriter写入十进制数时的结果: BinaryWriter....: E9 A8 94 23 9B CA 4E 44 63 C5 44 39 00 00 1A 00 unsafe *dec
decimal
s的en-和解码编写了测试。我的测试还包括.NET Framework的BinaryWriter
是否生成与我的BinaryWriter兼容的输出,反之亦然
我主要使用不安全和指针将变量写入字节数组。这些是通过指针和BinaryWriter写入十进制数时的结果:
BinaryWriter....: E9 A8 94 23 9B CA 4E 44 63 C5 44 39 00 00 1A 00
unsafe *decimal=: 00 00 1A 00 63 C5 44 39 E9 A8 94 23 9B CA 4E 44
我编写的十进制代码如下所示:
unsafe
{
byte[] data = new byte[16];
fixed (byte* pData = data)
*(decimal*)pData = 177.237846528973465289734658334m;
}
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(ms))
writer.Write(177.237846528973465289734658334m);
ms.ToArray();
}
使用.NET Framework的BinaryWriter如下所示:
unsafe
{
byte[] data = new byte[16];
fixed (byte* pData = data)
*(decimal*)pData = 177.237846528973465289734658334m;
}
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(ms))
writer.Write(177.237846528973465289734658334m);
ms.ToArray();
}
微软使他们的BinaryWriter
与decimal
存储在内存中的方式不兼容。通过查看referencesource,我们看到Microsoft使用了一个名为GetBytes的内部方法,这意味着GetBytes的输出与小数
存储在内存中的方式不兼容
微软以这种方式编写decimal
s有什么原因吗?使用不安全
的方式来实现自己的二进制格式或协议是否会很危险,因为小数的内部布局将来可能会发生变化
使用
unsafe
方式比使用BinaryWriter
调用的GetBytes
性能要好得多,微软自己也试图保持decimal
及其组件的对齐尽可能稳定。您还可以在所提到的.NET framework的referencesource中看到这一点:
// NOTE: Do not change the order in which these fields are declared. The
// native methods in this class rely on this particular order.
private int flags;
private int hi;
private int lo;
private int mid;
再加上[StructLayout(LayoutKind.Sequential)]
的使用,该结构在内存中以这种方式对齐
由于GetBytes
方法使用的变量在内部构建decimal
数据的顺序与结构本身的对齐顺序不同,因此会得到错误的结果:
internal static void GetBytes(Decimal d, byte[] buffer)
{
Contract.Requires((buffer != null && buffer.Length >= 16), "[GetBytes]buffer != null && buffer.Length >= 16");
buffer[0] = (byte)d.lo;
buffer[1] = (byte)(d.lo >> 8);
buffer[2] = (byte)(d.lo >> 16);
buffer[3] = (byte)(d.lo >> 24);
buffer[4] = (byte)d.mid;
buffer[5] = (byte)(d.mid >> 8);
buffer[6] = (byte)(d.mid >> 16);
buffer[7] = (byte)(d.mid >> 24);
buffer[8] = (byte)d.hi;
buffer[9] = (byte)(d.hi >> 8);
buffer[10] = (byte)(d.hi >> 16);
buffer[11] = (byte)(d.hi >> 24);
buffer[12] = (byte)d.flags;
buffer[13] = (byte)(d.flags >> 8);
buffer[14] = (byte)(d.flags >> 16);
buffer[15] = (byte)(d.flags >> 24);
}
在我看来,相应的.NET开发人员试图将GetBytes提供的格式调整为little endian,但犯了一个错误。他不仅对decimal
组件的字节进行排序,还对组件本身进行排序。(标志,hi,lo,mid变为lo,mid,hi,flags。)但小尾端布局仅适用于字段,而不是整个struct
s-尤其是[StructLayout(LayoutKind.Sequential)]
我在这里的建议通常是在他们的课堂上使用微软提供的方法。因此,我更喜欢任何基于GetBytes
或GetBits
的方式来序列化数据,而不是使用unsafe
进行序列化,因为微软将以任何方式保持与BinaryWriter
的兼容性。然而,这些评论有点严肃,我不希望微软在这个非常基本的层面上打破.NET框架
我很难相信性能对
不安全的比GetBits
更重要。毕竟我们在这里谈论的是十进制。您仍然可以通过safe
将GetBits
的int
推送到您的byte[]
可能重复的@GSerg,公平地说,参考答案只说明它是一种.NET特定格式。它并没有真正回答OP的问题。@Neijwiert好吧,没有人能回答OP的问题,即微软是否愿意改变这种格式。我们只能推测,出于兼容性的原因,这是极不可能的。@GSerg是真的,但我有点希望有人解释当前的实现是如何完成的,因为我不能解释。@Neijwiert看到了<代码>十进制-->十进制.GetBytes(),16字节,应能看到System.decimal类代码
。这是一个输入错误,应该是GetBits()
。decimal
的内部结构记录在。它还解释了为什么decimal.GetBits()
以特定顺序返回四个int
s的数组,以及它们的含义。我看不出哪里会有字节顺序不正确的错误。@GSerg在其他评论中已经提到:GetBits()
与此无关,因为。此外,链接文档没有说明为什么结构的内存布局与GetBytes()
返回的布局不同,而我的答案是这样的。你的推理是颠倒的GetBits()
是起点,因为它是有文档记录的。它返回的东西无法改变。从这一点出发,我们可以看到internal GetBytes()
返回的数据与文档中的GetBits()
返回的数据相同,但始终是小端格式(而GetBits()
自然会使用系统的当前端名),这对于具有不同端名的系统之间的可移植性是有意义的。这两种方法不会改变(这将破坏在假设的改变之前将十进制
s持久化到存储器中的能力)。相反,构成十进制
结构的私有字段的内部顺序,在任何时候都可能很容易改变,因为这纯粹是框架内部的问题——但实际上这不会发生,因为没有理由做出这样的改变(例如,引入新字段将破坏GetBits
和GetBytes
;如果必须这样做,它们将产生一种新类型,例如decimal2
)但即便如此,严格的答案是否定的,也不能保证通过将十进制*
转换为字节*
所看到的内容不会改变,而且你不应该依赖它。