如何在BinaryReader&;二进制编写器(.NET,C#)?

如何在BinaryReader&;二进制编写器(.NET,C#)?,c#,.net,unity3d,nodatime,binary-serialization,C#,.net,Unity3d,Nodatime,Binary Serialization,在我们主要使用Untiy engine开发的跨平台多应用网络系统中,我们使用自定义二进制序列化,即使用BinaryWriter简单地编写原语,然后使用镜像布局,使用BinaryReader读取原语。请注意,我们不使用BinaryFormatter,而是依赖于手动指定的二进制布局。它非常适合我们,因为我们可以绝对控制序列化的内容和方式。我们还使用对象的基本表示,而不是某种字符串格式,然后进行解析以保持其紧凑性。例如,我们将直接编写一个整数,而不是int.ToString()。换言之,紧凑超过了人类

在我们主要使用Untiy engine开发的跨平台多应用网络系统中,我们使用自定义二进制序列化,即使用BinaryWriter简单地编写原语,然后使用镜像布局,使用BinaryReader读取原语。请注意,我们不使用BinaryFormatter,而是依赖于手动指定的二进制布局。它非常适合我们,因为我们可以绝对控制序列化的内容和方式。我们还使用对象的基本表示,而不是某种字符串格式,然后进行解析以保持其紧凑性。例如,我们将直接编写一个整数,而不是int.ToString()。换言之,紧凑超过了人类可读性。Bare minimum可以明确地反序列化某些内容,使其返回到所序列化的内容

最近,我偶然发现NodaTime是一个“比DateTime、DateTimeOffset、TimeZoneInfo更好的”(.NET)解决方案,可以处理所有的时间问题。我特别喜欢类型的严格性,迫使我思考我到底在说什么(干净的宇宙时间线与曲折的时区疯狂等等)

我将日期和时间管理移到了NodaTime,但现在我很难将Noda类型序列化为二进制流作为原语。似乎没有“标准”的属性访问范式。有些类型有getter,比如Duration.TotalNanoseconds,有些类型有a'la ToXyz()方法,比如Instant.ToUnixTicks()。顺便说一句,这是我建议简化的:用于纯粹访问类型的可预期裸骨表示的getter和用于转换计算的a'la ToXyz()方法。例如,对于Instant类型,缺少此选项

我确实用野田佳彦的格式模式尝试了格式/解析方法,但出于某种原因,大多数提供的格式模式都不是往返模式,甚至抛出一个例外,即这些模式不支持解析,只支持格式:

binaryWriter.Write(ZonedDateTimePattern.ExtendedFormatOnlyIso.Format(值));
ZonedDateTimePattern.ExtendedFormatOnlyIso.Parse(binaryReader.ReadString()).Value;
我想要的是这么简单的东西:

binaryWriter.Write(瞬间。纳秒);
instant=新的instant(binaryReader.ReadDouble());
binaryWriter.Write(持续时间纳秒);
持续时间=新的持续时间(binaryReader.ReadDouble());
我可以使用哪种一致的、最好是非格式化的基于往返解析的方法来实现所有节点时间类型的紧凑二进制序列化

如果不可能,那么对于每种节点时间类型,推荐的往返模式是什么


PS:代码示例中的文本模式明确表示它们不是名称中的往返(仅格式,无解析)。我的错。

我们在NodeTime 1.x和2.x中直接支持二进制序列化,但这将从3.x中删除

如果您想直接编写,我建议您创建扩展方法以简化此操作:

public static void WriteInstant(this BinaryWriter writer, Instant instant)
这样你就可以编写
writer.WriteInstant(即时)并在单个位置隔离精确格式。下面是一个未经测试的实现:

public static class BinaryWriterNodaTimeExtensions
{
    public static void WriteInstant(this BinaryWriter writer, Instant instant) =>
        writer.WriteDuration(instant - NodaConstants.UnixEpoch);

    public static void WriteDuration(this BinaryWriter writer, Duration duration)
    {
        writer.Write(duration.Days);
        writer.Write(duration.NanosecondOfDay);

        // Alternative implementation if you don't need durations bigger than +/- 292 years
        // writer.Write(duration.ToInt64Nanoseconds());
    }

    public static void WriteLocalDateTime(this BinaryWriter writer, LocalDateTime localDateTime)
    {
        writer.WriteLocalDate(localDateTime.Date);
        writer.WriteLocalTime(localDateTime.TimeOfDay);
    }

    public static void WriteZonedDateTime(this BinaryWriter writer, ZonedDateTime zonedDateTime)
    {
        writer.WriteLocalDateTime(zonedDateTime.LocalDateTime);
        // Note: no indication of the DateTimeZoneProvider. There's no standard way of representing
        // that, but most applications would use the same one everywhere.
        writer.Write(zonedDateTime.Zone.Id);
        writer.WriteOffset(zonedDateTime.Offset);
    }

    public static void WriteLocalDate(this BinaryWriter writer, LocalDate localDate)
    {
        // Casting to byte to optimize for space, as requested in the question.
        writer.Write((byte) localDate.Year);
        writer.Write((byte) localDate.Month);
        writer.Write((byte) localDate.Day);
        // You could omit this if you'll only ever use the ISO calendar.
        writer.Write(localDate.Calendar.Id);
    }

    public static void WriteLocalTime(this BinaryWriter writer, LocalTime localTime) =>
        writer.Write(localTime.NanosecondOfDay);

    public static void WriteOffset(this BinaryWriter writer, Offset offset) =>
        writer.Write(offset.Seconds);

    public static void WriteOffsetDateTime(this BinaryWriter writer, OffsetDateTime offsetDateTime)
    {
        writer.WriteLocalDateTime(offsetDateTime.LocalDateTime);
        writer.WriteOffset(offsetDateTime.Offset);
    }
}
这并没有涵盖所有类型,但可能是您最需要的类型。(其他人可以很容易地以类似的方式创建。)读卡器端将类似,但正好相反。在阅读持续时间时,您可能会创建两个持续时间,一个为天,一个为纳秒,然后将它们相加

要对API设计说明进行评论,请执行以下操作:

似乎没有“标准”的属性访问范式。有些类型有getter,比如Duration.TotalNanoseconds,有些类型有a'la ToXyz()方法,比如Instant.ToUnixTicks()。顺便说一句,这是我建议简化的:用于纯粹访问类型的可预期裸骨表示的getter和用于转换计算的a'la ToXyz()方法。例如,对于Instant类型,缺少此选项


那完全是故意的。持续时间内的总纳秒数是它固有的。由于Unix纪元的引入,
即时
(etc)的“自Unix纪元以来的滴答数”是人为的。我们碰巧在内部使用了Unix epoch,但我不想让它溢出到API设计中——因此,它被建模为一种方法,使其具有更明确的“类似转换”的感觉。我不会将
Nanoseconds
属性(甚至
UnixNanoseconds
)添加到
Instant

你好,Jon。非常感谢您的详细解释。这很好地展示了写作部分。那我怎么做阅读部分呢?例如,对于Duration类,似乎没有合适的构造函数从二进制读取器上的读取值实例化。白天和纳秒也不是设定者(它们只是获取者)。我应该改用FromXys()方法之一吗?再说一次,他们都是从一个单位,而不是从多个?然后我应该使用Duration.Add(Duration.FromDays(),Duration.fromnoseconds())吗?谢谢@StepanStulov:根据答案:“在读取持续时间时,您可能会创建两个持续时间,一个为天,一个为纳秒,然后将它们相加。”不,没有任何设置器,因为Noda Time中的几乎所有内容都是不可变的。关于LocalTime类序列化:如何从纳秒恢复实例?找不到任何LocalTime.FromNanosecondsOfDay()方法或任何相应的构造函数。谢谢@StepanStulov:是的,这是2.x中API的一个不幸缺失部分(在3.x中)。幸运的是,这仍然很容易做到,只是有点模糊:
LocalTime结果=LocalTime.Midnight.PlusNanoseconds(纳秒)