Как использовать NodaTime с BinaryReader и BinaryWriter (.NET, C #)? - PullRequest
1 голос
/ 11 июля 2019

В нашей кроссплатформенной сетевой системе с несколькими приложениями, разработанной в основном с движком Untiy, мы используем настраиваемую двоичную сериализацию, которая просто записывает примитивы с помощью BinaryWriter, а затем, используя зеркальную разметку, читает то же самое с помощью BinaryReader.Обратите внимание, что мы не используем BinaryFormatter и вместо этого полагаемся на заданные вручную двоичные макеты.Это работает очень хорошо для нас, так как мы имеем абсолютный контроль над тем, что и как сериализуется.Мы также используем примитивные представления объектов, а не какое-то форматирование строк, а затем анализируем, чтобы сохранить их компактность.Так, например, мы бы написали целое число напрямую, а не int.ToString ().Другими словами, компактный, читаемый человеком.Голый минимум, чтобы однозначно десериализовать что-то обратно к тому, что было сериализовано.

Недавно я наткнулся на NodaTime как «лучшее» (.NET, чем DateTime, DateTimeOffset, TimeZoneInfo) решение для работы со всеми вещами времени.Мне особенно нравится строгость типов, заставляющая меня думать, о чем именно я говорю (чистая универсальная временная линия против зигзагообразного безумия часовых поясов и т. Д.).

Я перенес наше управление датой и временем в NodaTimeно теперь у меня проблемы с сериализацией типов Noda в двоичный поток как примитивы.Кажется, нет «стандартной» модели доступа к свойствам.Некоторые типы имеют геттеры, такие как Duration.TotalNanoseconds, некоторые вместо этого имеют методы a'la ToXyz (), такие как Instant.ToUnixTicks ().Кстати, это то, что я предлагаю упростить: методы получения для чистого доступа к ожидаемому представлению типа и методы a'la ToXyz () для вычисления преобразования.Это отсутствует, например, для типа Instant.

Я попробовал подход форматирования / синтаксического анализа с шаблонами формата Noda, но по какой-то причине большинство предоставленных шаблонов формата не являются двусторонним или даже выдают исключение, что эти шаблоны не используютне поддерживает синтаксический анализ, только форматирование:

binaryWriter.Write(ZonedDateTimePattern.ExtendedFormatOnlyIso.Format(value));
ZonedDateTimePattern.ExtendedFormatOnlyIso.Parse(binaryReader.ReadString()).Value;

Что я хочу, так это просто:

binaryWriter.Write(instant.Nanoseconds);
instant = new Instant(binaryReader.ReadDouble());

binaryWriter.Write(duration.nanoseconds);
duration= new Duration(binaryReader.ReadDouble());

Какой согласованный, предпочтительно не форматирующий-на основе анализа-туда-обратноПодход можно использовать для достижения компактной двоичной сериализации всех типов NodaTime?

Если это невозможно, какие рекомендуются скоростные паттерны для каждого типа NodaTime?

PS: Шаблоны текста вВ примере кода явно говорится, что они не являются круговыми (только для формата, без разбора) в своем имени.Мой плохой.

1 Ответ

2 голосов
/ 12 июля 2019

Мы поддерживаем двоичную сериализацию непосредственно в NodaTime 1.x и 2.x, но она будет удалена из 3.x.

Если вы хотите писать напрямую, я бы предложил создать методы расширения, чтобы сделатьэто просто:

public static void WriteInstant(this BinaryWriter writer, Instant instant)

и т. д.Таким образом, вы можете написать writer.WriteInstant(instant); и выделить точный формат в одном месте.Вот непроверенная реализация:

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:

Кажется, не существует "стандартной" парадигмы доступа к свойству.Некоторые типы имеют геттеры, такие как Duration.TotalNanoseconds, некоторые вместо этого имеют методы a'la ToXyz (), такие как Instant.ToUnixTicks ().Кстати, это то, что я предлагаю упростить: методы получения для чистого доступа к ожидаемому представлению типа и методы a'la ToXyz () для вычисления преобразования.Это отсутствует, например, для типа Instant.

Это совершенно умышленно.Ему присуще общее количество наносекунд в длительности.«Количество тиков со времени эпохи Unix» Instant (и т. Д.) Является искусственным вследствие введения эпохи Unix в этот вопрос.Мы случайно используем эпоху Unix для внутреннего использования, но я не хотел, чтобы это вылилось в дизайн API - поэтому он смоделирован как метод, чтобы придать ему более явное «конверсионное» чувство.Я не буду добавлять Nanoseconds свойство (или даже UnixNanoseconds) к Instant.

...