*(decimal*)d=XXXm 产生不同于 BinaryWriter.Write(XXXm) 的另一个输出

*(decimal*)d=XXXm results in another output than BinaryWriter.Write(XXXm)

我正在编写一个优化的二进制文件 reader/writer 以供我自己学习。一切正常,直到我编写了 decimals 的编码和解码测试。我的测试还包括 .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;
}

使用 .NET Framework 的 BinaryWriter 如下所示:

using (MemoryStream ms = new MemoryStream())
{
    using (BinaryWriter writer = new BinaryWriter(ms))
        writer.Write(177.237846528973465289734658334m);

    ms.ToArray();
}

Microsoft 使其 BinaryWriterdecimal 存储在内存中的方式不兼容。通过查看参考源,我们发现微软使用了一个名为 GetBytes 的内部方法,这意味着 GetBytes 的输出与 decimals 存储在内存中的方式不兼容。

Microsoft 以这种方式实现写入 decimal 是否有原因?使用 unsafe 的方式来实现自己的二进制格式或协议可能是危险的,因为将来小数的内部布局可能会改变?

使用 unsafe 方式比使用 BinaryWriter 调用的 GetBytes 执行得更好。

Microsoft 本身试图保持 decimal 及其组件的对齐尽可能稳定。您还可以在提到的 .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 方法使用在内部构建 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 提供的格式适配为小端,但犯了一个错误。他不仅对 decimal 的组件的字节进行排序,而且对组件本身进行排序。 (flags, hi, lo, mid 变为 lo, mid, hi, flags。)但是小端布局只适用于不完整的字段 structs - 特别是 [StructLayout(LayoutKind.Sequential)].

我的建议通常是使用 Microsoft 在 类 中提供的方法。因此,与使用 unsafe 相比,我更喜欢任何基于 GetBytesGetBits 的数据序列化方式,因为微软将以任何方式保持与 BinaryWriter 的兼容性。但是,评论有点严重,我不希望微软在这个非常基础的层面上破坏 .NET 框架。

我很难相信性能如此重要,以至于 unsafeGetBits 更受青睐。毕竟我们在这里谈论的是decimals。您仍然可以通过 unsafeGetBitsint 推送到您的 byte[].