*(decimal *)d = XXXm导致另一个输出不同于BinaryWriter.Write(XXXm)

时间:2019-01-02 14:41:14

标签: c# decimal unsafe binarywriter

我正在编写一个优化的二进制读取器/写入器,用于自己学习。一切工作正常,直到我编写了decimal的编码和解码测试。我的测试还包括.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的存储方式不兼容。通过查看参考源,我们可以看到Microsoft使用了一种称为GetBytes的内部方法,这意味着GetBytes的输出与decimals在内存中的存储方式不兼容。

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

使用unsafe的方式比使用GetBytes调用的BinaryWriter的效果要好。

1 个答案:

答案 0 :(得分:2)

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方法使用的变量是错误的结果,这些变量在内部按自身在结构中对齐的顺序在内部{em> not 中构建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的字段-尤其是[StructLayout(LayoutKind.Sequential)]

我在这里的建议通常是使用Microsoft在其类中提供的方法。因此,与使用GetBytes相比,我更喜欢任何基于GetBitsunsafe的序列化数据的方式,因为Microsoft将以任何方式保持与BinaryWriter的兼容性。但是,这些评论有点严肃,我不希望Microsoft在这个非常基本的水平上破坏.NET框架。

我很难相信性能非常重要,以unsafe胜过GetBits。毕竟,我们在这里谈论decimal。您仍然可以通过intGetBits的{​​{1}}推入unsafe