我正在为自己的学习目的编写一个优化的二进制读取器/写入器.一切正常,直到我为小数的编码和解码编写测试.我的测试还包括.NET Framework的BinaryWriter是否为我的BinaryWriter生成兼容的输出,反之亦然.
我主要使用unsafe和指针将变量写入字节数组.当通过指针和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;
}
使用.NET Framework的BinaryWriter看起来像这样:
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(ms))
writer.Write(177.237846528973465289734658334m);
ms.ToArray();
}
Microsoft使他们的BinaryWriter与小数存储在内存中的方式不兼容.通过查看referencesource,我们看到Microsoft使用名为GetBytes的内部方法,这意味着GetBytes的输出与小数存储在内存中的方式不兼容.
微软是否有理由以这种方式实现写小数?使用不安全的方式实现自己的二进制格式或协议可能是危险的,因为小数的内部布局将来可能会改变吗?
使用不安全的方式比使用BinaryWriter调用的GetBytes要好得多.
解决方法:
微软本身试图保持小数和它的组件对齐尽可能稳定.您还可以在.NET框架的上述引用源中看到这一点:
// 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方法使用的是在内部构建小数数据的变量而不是它们在结构本身中对齐的顺序:
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提供的格式改编为小端,但是犯了一个错误.他不仅订购了小数组件的字节,还订购了组件本身. (flags,hi,lo,mid变为lo,mid,hi,flags.)但是小端布局仅适用于不是整个结构的字段 – 尤其是[StructLayout(LayoutKind.Sequential)].
我的建议通常是使用Microsoft在其课程中提供的方法.因此,我希望任何基于GetBytes或GetBits的方式来序列化数据,而不是使用不安全的方式,因为Microsoft将以任何方式保持与BinaryWriter的兼容性.但是,这些评论有点严肃,我不希望微软在这个基础层面打破.NET框架.
我很难相信性能对于支持GetBits的不安全方式非常重要.毕竟我们在这里谈论小数.你仍然可以通过unsafe将GetBits的int推入你的byte [].