1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-MessagePack-CSharp

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

MessagePack для C# (.NET, .NET Core, Unity, Xamarin)

MessagePack — это чрезвычайно быстрый сериализатор для языка C#. Он в 10 раз быстрее, чем MsgPack-Cli, и превосходит другие сериализаторы C#. MessagePack для C# также поставляется со встроенной поддержкой LZ4-сжатия — чрезвычайно быстрого алгоритма сжатия. Производительность важна, особенно в таких приложениях, как игры, распределённые вычисления, микросервисы или кэши данных.

На рисунке ниже представлено сравнение производительности MessagePack с другими форматами данных:

  • MessagePack — формат данных, разработанный для эффективной работы с двоичными данными;
  • JSON — текстовый формат обмена данными, основанный на JavaScript;
  • Protobuf — бинарный формат обмена данными от Google;
  • ZeroFormatter — библиотека для сериализации объектов в C#.

MessagePack имеет компактный двоичный размер и полный набор универсальных выразительных типов данных. Пожалуйста, ознакомьтесь с разделом «Сравнение с JSON, protobuf, ZeroFormatter» и узнайте, почему MessagePack C# является самым быстрым.

Содержание

  • Установка
    • Пакеты NuGet.
    • Unity.
    • Миграция с версии 1.x.
  • Быстрый старт.
  • Анализатор.
  • Встроенные поддерживаемые типы.
  • Сериализация объектов.
  • Совместимость DataContract.
  • Сериализация readonly/immutable членов объекта (SerializationConstructor).
  • Обратный вызов сериализации.
  • Объединение.
  • Динамическая (нетипизированная) десериализация.
  • Сериализация типа объекта.
  • Typeless.
  • Безопасность.
  • Производительность
    • Производительность десериализации для различных параметров.
    • Интернирование строк.
  • LZ4-сжатие.
    • Атрибуции.
  • Сравнение с protobuf, JSON, ZeroFormatter.
  • Советы по достижению максимальной производительности при использовании MessagePack для C#.
    • Используйте индексированные ключи вместо строковых ключей (Contractless).
    • Создайте собственный пользовательский составной преобразователь.
    • Используйте собственные преобразователи.
    • Будьте осторожны при копировании буферов.
    • Выбор сжатия.
  • Расширения.
  • Экспериментальные функции.
  • Высокоуровневый API (MessagePackSerializer).
    • Несколько структур MessagePack в одном потоке.
  • Низкоуровневый API (IMessagePackFormatter).
  • Примитивный API (MessagePackWriter, MessagePackReader).
    • MessagePackReader.
    • MessagePackWriter.
  • Основная точка расширения (IFormatterResolver).
  • MessagePackFormatterAttribute.
  • IgnoreFormatter.
  • Зарезервированное расширение. Типы
  • Поддержка Unity.
  • Генерация кода AOT (поддержка Unity/Xamarin).
  • RPC:
    • MagicOnion.
    • StreamJsonRpc.

Как собрать

Эта библиотека распространяется через NuGet. Также доступна специальная поддержка Unity.

Мы ориентируемся на .NET Standard 2.0 со специальными оптимизациями для .NET Core 2.1+ и делаем его совместимым с большинством современных сред выполнения .NET, таких как Core 2.0 и более поздние версии, Framework 4.6.1 и более поздние, Mono 5.4 и более поздние и Unity 2018.3 и более поздние. Код библиотеки написан на чистом C# (с генерацией кода IL «на лету» на некоторых платформах).

Пакеты NuGet

Чтобы установить с помощью NuGet, просто установите пакет MessagePack:

Install-Package MessagePack

Установите дополнительный пакет анализаторов C#, чтобы получать предупреждения об ошибках кодирования и автоматические предложения по исправлению, которые помогут вам сэкономить время:

Install-Package MessagePackAnalyzer

Также доступно множество официальных и сторонних пакетов расширений (подробнее читайте в разделе расширения):

Install-Package MessagePack.ReactiveProperty
Install-Package MessagePack.UnityShims
Install-Package MessagePack.AspNetCoreMvcFormatter

Unity

Для проектов Unity на странице релизов доступны файлы .unitypackage. При использовании в средах Unity IL2CPP или Xamarin AOT внимательно прочитайте раздел предварительной генерации кода.

Примечания к миграции с версии v1.x

Если вы использовали MessagePack для C# версии 1.x, ознакомьтесь с документом «Как обновить до нашей новой версии v2.x».

Быстрый старт

Определите структуру или класс для сериализации и пометьте его атрибутом [MessagePackObject]. Пометьте члены, значения которых должны быть сериализованы (поля, а также свойства), атрибутами [Key].

[MessagePackObject]
public class MyClass
{
    // Атрибуты Key принимают индекс сериализации (или строковое имя).
    // Значения должны быть уникальными, и необходимо учитывать управление версиями.
    // Ключи описаны в следующих разделах более подробно.
    [Key(0)]
    public int Age { get; set; }

    [Key(1)]
    public string FirstName { get; set; }

    [Key(2)]
    public string LastName { get; set; }

    // Все поля или свойства, которые не должны быть сериализованы, должны быть помечены атрибутом [IgnoreMember].
    [IgnoreMember]
    public string FullName { get { return FirstName + LastName; } }
}

Вызовите MessagePackSerializer.Serialize<T>/Deserialize<T>, чтобы сериализовать/десериализовать экземпляр объекта. Вы можете использовать метод ConvertToJson для получения удобочитаемого представления любого двоичного большого двоичного объекта MessagePack.

class Program
{
    static void Main(string[] args)
    {
        var mc = new MyClass
        {
            Age = 99,
            FirstName = "hoge",
            LastName = "huga",
        };

        // Вызов Serialize/Deserialize, вот и всё.
        byte[] bytes = MessagePackSerializer.Serialize(mc);
        MyClass mc2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

        // Можно выгрузить двоичные большие двоичные объекты MessagePack в удобочитаемый json.
        // Использование индексированных ключей (в отличие от строковых ключей) приведёт к сериализации массивов MessagePack,
        // поэтому имена свойств недоступны.
        // [99,"hoge","huga"]
        var json = MessagePackSerializer.ConvertToJson(bytes);
        Console.WriteLine(json);
    }
}

По умолчанию требуется аннотация MessagePackObject. Это можно сделать необязательным; подробности см. в разделах Сериализация объектов и Форматировщик Resolver.

Анализатор

Пакет MessagePackAnalyzer помогает:

  1. Автоматизировать определения для ваших сериализуемых объектов.
  2. Генерировать предупреждения компилятора при неправильном использовании атрибутов, доступности членов и т. д. Сериализация объектов

MessagePack для C# может сериализовать ваши собственные открытые типы class или struct. По умолчанию сериализуемые типы должны быть аннотированы атрибутом [MessagePackObject], а члены — атрибутом [Key]. Ключи могут быть либо индексами (int), либо произвольными строками. Если все ключи являются индексами, то для сериализации используются массивы, что даёт преимущества в производительности и размере двоичного файла. В противном случае будут использоваться карты MessagePack (словари).

Если вы используете [MessagePackObject(keyAsPropertyName: true)], то членам не требуются явные атрибуты Key, но будут использоваться строковые ключи.

[MessagePackObject]
public class Sample1
{
    [Key(0)]
    public int Foo { get; set; }
    [Key(1)]
    public int Bar { get; set; }
}

[MessagePackObject]
public class Sample2
{
    [Key("foo")]
    public int Foo { get; set; }
    [Key("bar")]
    public int Bar { get; set; }
}

[MessagePackObject(keyAsPropertyName: true)]
public class Sample3
{
    // Нет необходимости в атрибуте Key
    public int Foo { get; set; }

    // Если хотите проигнорировать открытый член, можно использовать атрибут IgnoreMember
    [IgnoreMember]
    public int Bar { get; set; }
}

// [10,20]
Console.WriteLine(MessagePackSerializer.SerializeToJson(new Sample1 { Foo = 10, Bar = 20 }));

// {"foo":10,"bar":20}
Console.WriteLine(MessagePackSerializer.SerializeToJson(new Sample2 { Foo = 10, Bar = 20 }));

// {"Foo":10}
Console.WriteLine(MessagePackSerializer.SerializeToJson(new Sample3 { Foo = 10, Bar = 20 }));

Все открытые члены экземпляра (поля, а также свойства) будут сериализованы. Если вы хотите игнорировать определённые открытые члены, аннотируйте член атрибутом [IgnoreMember].

Обратите внимание, что любой сериализуемый тип структуры или класса должен иметь публичную доступность; частные и внутренние структуры и классы не могут быть сериализованы! Требование по умолчанию наличия аннотаций MessagePackObject призвано обеспечить ясность и, следовательно, может помочь написать более надёжный код.

Следует ли вам использовать индексированный (int) ключ или строковый ключ? Мы рекомендуем использовать индексированные ключи для более быстрой сериализации и более компактного двоичного представления, чем строковые ключи. Строковые ключи могут быть весьма полезны при отладке.

При изменении или расширении классов будьте осторожны с управлением версиями. MessagePackSerializer инициализирует члены значением по умолчанию, если ключ не существует в сериализованном двоичном большом двоичном объекте, что означает, что члены, использующие ссылочные типы, могут быть инициализированы значением null. Если вы используете индексированные (int) ключи, то они должны начинаться с 0 и должны быть последовательными. Если более поздняя версия перестанет использовать определённые члены, вы должны сохранить устаревшие члены (C# предоставляет атрибут Obsolete для аннотирования таких членов), пока у всех остальных клиентов не будет возможности обновить и удалить использование этих членов. Кроме того, когда значения индексированных ключей «прыгают» сильно, оставляя пробелы в последовательности, это негативно скажется на размере двоичного файла, так как в результирующие массивы будут вставлены заполнители null. Однако вам не следует повторно использовать индексы удалённых членов, чтобы избежать проблем совместимости между клиентами или при попытке десериализации устаревших больших двоичных объектов.

Пример пробелов в индексах и получающихся заполнителей:

[MessagePackObject]
public class IntKeySample
{
    [Key(3)]
    public int A { get; set; }
    [Key(10)]
    public int B { get; set; }
}

// [null,null,null,0,null,null,null,null,null,null,0]
Console.WriteLine(MessagePackSerializer.SerializeToJson(new IntKeySample()));

Если вы не хотите явно аннотировать атрибутами MessagePackObject/Key и вместо этого хотите использовать MessagePack для C# больше похоже, например, на Json.NET, вы можете воспользоваться бесконтрактным распознавателем.

public class ContractlessSample
{
    public int MyProperty1 { get; set; }
    public int MyProperty2 { get; set; }
}

var data = new ContractlessSample { MyProperty1 = 99, MyProperty2 = 9999 };
var bin = MessagePackSerializer.Serialize(
  data,
  MessagePack.Resolvers.ContractlessStandardResolver.Options);

// {"MyProperty1":99,"MyProperty2":9999}
Console.WriteLine(MessagePackSerializer.ConvertToJson(bin));

// Вы также можете установить ContractlessStandardResolver в качестве значения по умолчанию.
// (Глобальное состояние; Не рекомендуется при написании кода библиотеки)
MessagePackSerializer.DefaultOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options;

// Теперь сериализуемо...
var bin2 = MessagePackSerializer.Serialize(data);

Если вы хотите сериализовать также и приватные члены, вы можете использовать один из AllowPrivate распознавателей.

[MessagePackObject]
public class PrivateSample
{
    [Key(0)]
    int x;

    public void SetX(int v)
    {
        x = v;
    }

    public int GetX()
    {
        return x;
    }
}

var data = new PrivateSample();
data.SetX(9999);

// Вы можете выбрать либо StandardResolverAllowPrivate,
// либо ContractlessStandardResolverAllowPrivate
var bin = MessagePackSerializer.Serialize(
  data,
  MessagePack.Resolvers.DynamicObjectResolverAllowPrivate.Options);

Если вы хотите использовать MessagePack для C# больше как BinaryFormatter с типизированным API сериализации, используйте типизированный распознаватель и вспомогательные функции. Пожалуйста, обратитесь к разделу Typeless. Распознаватели — это способ добавить специализированную поддержку пользовательских типов в MessagePack для C#. Пожалуйста, обратитесь к разделу Extension point.

Совместимость с DataContract

Вы можете использовать аннотации [DataContract] вместо [MessagePackObject]. Если тип аннотирован DataContract, вы можете использовать аннотацию [DataMember] вместо [Key] и [IgnoreDataMember] вместо [IgnoreMember]. Тогда [DataMember(Order = int)] будет вести себя так же, как [Key(int)], [DataMember(Name = string)] — так же, как [Key(string)], а [DataMember] — так же, как [Key(nameof(member name))]. Использование DataContract, например, в общих библиотеках делает ваши классы/структуры независимыми от сериализации MessagePack для C#. Однако это не поддерживается анализаторами и в генерации кода инструментом mpc. Также такие функции, как UnionAttribute, MessagePackFormatter, SerializationConstructor и т. д., не могут быть использованы. По этой причине мы рекомендуем вам использовать специфические аннотации MessagePack для C#, когда это возможно. Сериализация readonly/immutable object members (SerializationConstructor)

MessagePack для C# поддерживает сериализацию readonly/immutable объектов/членов. Например, эту структуру можно сериализовать и десериализовать.

[MessagePackObject]
public struct Point
{
    [Key(0)]
    public readonly int X;
    [Key(1)]
    public readonly int Y;

    public Point(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

var data = new Point(99, 9999);
var bin = MessagePackSerializer.Serialize(data);

// Можно десериализовывать неизменяемый объект
var point = MessagePackSerializer.Deserialize<Point>(bin);

MessagePackSerializer выберет конструктор с наилучшим соответствием списку аргументов, используя индексы аргументов для индексных ключей или имена параметров для строковых ключей. Если он не может определить подходящий конструктор, будет выброшено исключение MessagePackDynamicObjectResolverException: can't find matched constructor parameter. Вы можете указать, какой конструктор использовать вручную, с помощью аннотации [SerializationConstructor].

[MessagePackObject]
public struct Point
{
    [Key(0)]
    public readonly int X;
    [Key(1)]
    public readonly int Y;

    [SerializationConstructor]
    public Point(int x)
    {
        this.X = x;
        this.Y = -1;
    }

    // Если атрибут не отмечен, используется этот (наиболее подходящий аргумент)
    public Point(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

Типы записей C# 9

Запись C# 9 с первичным конструктором похожа на неизменяемый объект и также поддерживает сериализацию/десериализацию.

// Используйте ключ в качестве имени свойства
[MessagePackObject(true)] public record Point(int X, int Y);

// Использовать свойство: для установки KeyAttribute
[MessagePackObject] public record Point([property:Key(0)] int X, [property: Key(1)] int Y);

// Или используйте явные свойства
[MessagePackObject]
public record Person
{
    [Key(0)]
    public string FirstName { get; init; }

    [Key(1)]
    public string LastName { get; init; }
}

Ограничения установщика свойств init в C# 9

При использовании установщиков свойств init в универсальных классах ошибка CLR препятствует нашему наиболее эффективному созданию кода от вызова установщика свойств. В результате вам следует избегать использования init для установщиков свойств в универсальных классах при использовании общедоступных DynamicObjectResolver/StandardResolver. Когда используются резолверы DynamicObjectResolverAllowPrivate/StandardResolverAllowPrivate, ошибка не применяется, и вы можете использовать init без ограничений.

Сериализационный обратный вызов

Объекты, реализующие интерфейс IMessagePackSerializationCallbackReceiver, будут получать вызовы OnBeforeSerialize и OnAfterDeserialize во время сериализации/десериализации.

[MessagePackObject]
public class SampleCallback : IMessagePackSerializationCallbackReceiver
{
    [Key(0)]
    public int Key { get; set; }

    public void OnBeforeSerialize()
    {
        Console.WriteLine("OnBefore");
    }

    public void OnAfterDeserialize()
    {
        Console.WriteLine("OnAfter");
    }
}

Union

MessagePack для C# поддерживает сериализацию объектов, типизированных интерфейсом, и абстрактных классов. Это похоже на XmlInclude или ProtoInclude. В MessagePack для C# они называются Union. Только интерфейсам и абстрактным классам разрешено аннотировать атрибуты Union. Требуются уникальные ключи объединения.

// Аннотируйте типы наследования
[MessagePack.Union(0, typeof(FooClass))]
[MessagePack.Union(1, typeof(BarClass))]
public interface IUnionSample
{
}

[MessagePackObject]
public class FooClass : IUnionSample
{
    [Key(0)]
    public int XYZ { get; set; }
}

[MessagePackObject]
public class BarClass : IUnionSample
{
    [Key(0)]
{
    public string OPQ { get; set; }
}

// ---

IUnionSample data = new FooClass() { XYZ = 999 };

// Сериализуйте объект, типизированный интерфейсом.
var bin = MessagePackSerializer.Serialize(data);

// Десериализуйте снова.
var reData = MessagePackSerializer.Deserialize<IUnionSample>(bin);

// Используйте, например, переключение типов в C# 7.0
switch
``` (reData)
{
    case FooClass x:
        Console.WriteLine(x.XYZ);
        break;
    case BarClass x:
        Console.WriteLine(x.OPQ);
        break;
    default:
        break;
}

Unions are internally serialized to two-element arrays.

IUnionSample data = new BarClass { OPQ = "FooBar" };

var bin = MessagePackSerializer.Serialize(data);

// Union is serialized to two-length array, [key, object]
// [1,["FooBar"]]
Console.WriteLine(MessagePackSerializer.ConvertToJson(bin));

Using Union with abstract classes works the same way.

[Union(0, typeof(SubUnionType1))]
[Union(1, typeof(SubUnionType2))]
[MessagePackObject]
public abstract class ParentUnionType
{
    [Key(0)]
    public int MyProperty { get; set; }
}

[MessagePackObject]
public class SubUnionType1 : ParentUnionType
{
    [Key(1)]
    public int MyProperty1 { get; set; }
}

[MessagePackObject]
public class SubUnionType2 : ParentUnionType
{
    [Key(1)]
    public int MyProperty2 { get; set; }
}

Please be mindful that you cannot reuse the same keys in derived types that are already present in the parent type, as internally a single flat array or map will be used and thus cannot have duplicate indexes/keys.

Динамическая (нетипизированная) десериализация

При вызове MessagePackSerializer.Deserialize<object> или MessagePackSerializer.Deserialize<dynamic> любые значения, присутствующие в большом двоичном объекте, будут преобразованы в примитивные значения, т. е. bool, char, sbyte, byte, short, int, long, ushort, uint, ulong, float, double, DateTime, string, byte[], object[], IDictionary<object, object>.

// Sample blob.
var model = new DynamicModel { Name = "foobar", Items = new[] { 1, 10, 100, 1000 } };
var blob = MessagePackSerializer.Serialize(model, ContractlessStandardResolver.Options);

// Динамический («нетипизированный»)
var dynamicModel = MessagePackSerializer.Deserialize<dynamic>(blob, ContractlessStandardResolver.Options);

// Вы можете получить доступ к данным с помощью индексаторов массива/словаря, как показано выше
Console.WriteLine(dynamicModel["Name"]); // foobar
Console.WriteLine(dynamicModel["Items"][2]); // 100

Изучение деревьев объектов с использованием синтаксиса индексатора словаря — это самый быстрый вариант для нетипизированной десериализации, но его утомительно читать и писать. Если производительность не так важна, как читаемость кода, рассмотрите возможность десериализовать с помощью ExpandoObject.

Сериализация типа объекта

StandardResolver и ContractlessStandardResolver могут сериализовать object/анонимные типизированные объекты.

var objects = new object[] { 1, "aaa", new ObjectFieldType { Anything = 9999 } };
var bin = MessagePackSerializer.Serialize(objects);

// [1,"aaa",[9999]]
Console.WriteLine(MessagePackSerializer.ConvertToJson(bin));

// Поддержка анонимной сериализации типов
var anonType = new { Foo = 100, Bar = "foobar" };
var bin2 = MessagePackSerializer.Serialize(anonType, MessagePack.Resolvers.ContractlessStandardResolver.Options);

// {"Foo":100,"Bar":"foobar"}
Console.WriteLine(MessagePackSerializer.ConvertToJson(bin2));

Unity поддерживает ограничено.

При десериализации поведение будет таким же, как при динамической (нетипизированной) десериализации.

Нетипизированный

Нетипизированный API похож на BinaryFormatter, поскольку он будет встраивать информацию о типе в большие двоичные объекты, поэтому при вызове API типы не нужно указывать явно.

object mc = new Sandbox.MyClass()
{
    Age = 10,
    FirstName = "hoge",
    LastName = "huga"
};

// Сериализуем с помощью нетипизированного API
var blob = MessagePackSerializer.Typeless.Serialize(mc);

// В большом двоичном объекте есть встроенная информация о типе сборки.
// ["Sandbox.MyClass, Sandbox",10,"hoge","huga"]
Console.WriteLine(MessagePackSerializer.ConvertToJson(bin));

// Можно десериализовать обратно в MyClass с помощью нетипизированного API.
// Обратите внимание, что в вызове Deserialize тип не нужно указывать явно,
// поскольку информация о типе встроена в двоичный большой двоичный объект
var objModel = MessagePackSerializer.Typeless.Deserialize(bin) as MyClass;

Информация о типе представлена в формате MessagePack ext, код типа 100. MessagePackSerializer.Typeless — это сокращение от Serialize/Deserialize(TypelessContractlessStandardResolver.Instance).

Если вы хотите настроить его в качестве резолвера по умолчанию, можно использовать MessagePackSerializer.Typeless.RegisterDefaultResolver.

TypelessFormatter можно использовать отдельно или в сочетании с другими резолверами.

// Заменён `object`, используется типонезависимый резолвер
var resolver = MessagePack.Resolvers.CompositeResolver.Create(
    new[] { MessagePack.Formatters.TypelessFormatter.Instance },
    new[] { MessagePack.Resolvers.StandardResolver.Instance });

public class Foo
{
    // Использовать Typeless (только для этого поля)
    [MessagePackFormatter(typeof(TypelessFormatter))]
    public object Bar;
}

Если имя типа будет изменено позже, вы больше не сможете десериализовать старые большие двоичные объекты. Но в таких случаях вы можете указать резервное имя, предоставив собственную функцию TypelessFormatter.BindToType.

MessagePack.Formatters.TypelessFormatter.BindToType = typeName =>
{
    if (typeName.StartsWith("SomeNamespace"))
    {
        typeName = typeName.Replace("SomeNamespace", "AnotherNamespace");
    }

    return Type.GetType(typeName, false);
};

Безопасность

Десериализация данных из ненадёжного источника может привести к уязвимостям безопасности в вашем приложении. В зависимости от настроек, используемых во время десериализации, ненадёжные данные могут выполнять произвольный код или вызывать атаку отказа в обслуживании. Ненадёжные данные могут поступать из сети от ненадёжных источников (например, любых сетевых клиентов) или могут быть подделаны посредником при передаче через неаутентифицированное соединение, или из локального хранилища, которое могло быть подделано, или из многих других источников. MessagePack для C# не предоставляет никаких средств для аутентификации данных или защиты их от подделки. Пожалуйста, используйте подходящий метод аутентификации данных перед десериализацией, такой как MAC.

Пожалуйста, будьте очень внимательны к этим сценариям атак; многие проекты и компании, а также пользователи библиотек сериализации в целом сталкивались с проблемами десериализации ненадёжных пользовательских данных в прошлом.

При десериализации ненадёжных данных переведите MessagePack в более безопасный режим, настроив свойство MessagePackSerializerOptions.Security:

var options = MessagePackSerializerOptions.Standard
    .WithSecurity(MessagePackSecurity.UntrustedData);

// Передайте параметры явно для максимального контроля.
T object = MessagePackSerializer.Deserialize<T>(data, options);

// Или установите уровень безопасности по умолчанию.
MessagePackSerializer.DefaultOptions = options;

Также следует избегать типонезависимых сериализаторов/форматеров/резолверов для ненадёжных данных, поскольку это открывает дверь для ненадёжных данных потенциально десериализовывать непредвиденные типы, которые могут поставить под угрозу безопасность.

Режим UntrustedData просто защищает от некоторых распространённых атак, но сам по себе не является полностью безопасным решением.

Производительность

Тесты производительности MessagePack For C# по сравнению с другими сериализаторами проводились на Windows 10 Pro x64 Intel Core i7-6700K 4.00 ГГц, 32 ГБ ОЗУ. Код теста доступен здесь, а их информация о версии здесь. ZeroFormatter и FlatBuffers имеют бесконечно быстрые десериализаторы, поэтому их производительность десериализации игнорируется.

Рисунок.

MessagePack for C# использует множество методов для повышения производительности:

  • Сериализатор использует IBufferWriter вместо System.IO.Stream, чтобы уменьшить нагрузку на память.

  • Буферы арендуются из пулов для уменьшения выделения ресурсов, поддерживая высокую пропускную способность за счёт снижения давления сборщика мусора.

  • Не создавайте промежуточные служебные экземпляры (*Writer/Reader, Context и т. д.).

  • Используйте динамический код. Генерация и JIT для избежания упаковки типов значений. Используйте генерацию AOT на платформах, которые запрещают JIT.

  • Кэшируйте сгенерированные форматеры в статических общих полях (не используйте словарь-кэш, так как поиск по словарю создаёт дополнительную нагрузку). См. Resolvers.

  • Тщательно настройте динамическую генерацию IL-кода и JIT, чтобы избежать упаковки типов значений. См. DynamicObjectTypeBuilder. Используйте генерацию AOT на платформах, запрещающих JIT.

  • Вызывайте Primitive API напрямую, когда генерация IL-кода определяет целевые типы как примитивные.

  • Сокращайте ветвление форматов переменной длины, когда генерация IL-кода знает диапазоны целевых типов (целое число/строка).

  • Не используйте абстракцию IEnumerable<T> для перебора коллекций, где это возможно. См.: CollectionFormatterBase и производные коллекционные форматеры.

  • Используйте предварительно сгенерированные таблицы поиска для уменьшения проверок ограничений типа mgpack. См.: MessagePackBinary.

  • Используйте оптимизированный словарь ключей типов для неуниверсальных методов. См.: ThreadsafeTypeKeyHashTable.

  • Избегайте декодирования строковых ключей для карт поиска (используйте строковые ключи и автоматическую генерацию кода с помощью встроенного IL-кода). См.: AutomataDictionary.

  • Для кодирования строковых ключей используйте предварительно сгенерированный байт имени члена и копии байтового массива фиксированного размера в IL. См.: UnsafeMemory.cs.

Перед созданием этой библиотеки я реализовал быстрый сериализатор с ZeroFormatter#Performance. Это дальнейшая реализация. MessagePack для C# всегда быстр и оптимизирован для всех типов (примитивный, небольшой структуры, большой объект, любые коллекции).

Производительность десериализации для разных опций

Производительность зависит от используемых опций. Это микротест с использованием BenchmarkDotNet. Целевой объект имеет 9 членов (MyProperty1 ~ MyProperty9), значения равны нулю.

Метод Среднее значение Ошибка Масштабирование Генерация 0 Выделено
M IntKey 72,67 нс NA 1,00 0,0132 56 Б
М StringKey 217,95 нс NA 3,00 0,0131 56 Б
M Typeless_IntKey 176,71 нс NA 2,43 0,0131 56 Б
M Typeless_StringKey 378,64 нс NA 5,21 0,0129 56 Б
MsgPackCliMap 1 355,26 нс NA 18,65 0,1431 608 Б
MsgPackCliArray 455,28 нс NA 6,26 0,0415 176 Б
ProtobufNet 265,85 нс NA 3,66 0,0319 136 Б
Hyperion 366,47 нс NA 5,04 0,0949 400 Б
JsonNetString 2 783,39 нс NA 38,30 0,6790 2864 Б
JsonNetStreamReader 3 297,90 нс NA 45,38 1,4267 6000 Б
JilString 553,65 нс NA 7,62 0,0362 152 Б
JilStreamReader 1 408,46 нс NA 19,38 0,8450 3552 Б

IntKey, StringKey, Typeless_IntKey, Typeless_StringKey — это опции MessagePack для C#. Все опции MessagePack для C# обеспечивают нулевое выделение памяти. В процессе десериализации JsonNetString/JilString десериализуется из строк. JsonNetStreamReader/JilStreamReader десериализуется из массивов байтов UTF-8 с использованием StreamReader. Десериализация обычно считывается из потока. Таким образом, она будет восстановлена из массивов байтов (или потока) вместо строк.

MessagePack для C# IntKey является самым быстрым. StringKey медленнее, чем IntKey, потому что требуется сопоставление символьной строки имён свойств. IntKey работает, читая длину массива, затем for (длина массива) { двоичное декодирование }. StringKey работает, читая длину карты, for (длина карты) { декодирование ключа, поиск ключа, двоичное декодирование}, поэтому ему требуются дополнительные два шага (декодирование ключей и поиск ключей).

Строковый ключ часто является полезной, бесконтрактной, простой заменой JSON, обеспечивает взаимодействие с другими языками и более надёжное управление версиями. MessagePack для C# также оптимизирован для строковых ключей, насколько это возможно. Прежде всего, он не декодирует массивы байтов UTF-8 в полную строку для сопоставления с именем члена; вместо этого он ищет массивы байтов как есть (чтобы избежать затрат на декодирование и дополнительных выделений памяти).

Он попытается сопоставить каждый тип long (по 8 символов, если этого недостаточно, дополнить нулями), используя теорию автоматов и встраивая его при генерации кода IL.

Это также позволяет избежать вычисления хэш-кода массивов байтов, и сравнение может быть выполнено в несколько раз быстрее с использованием типа long.

Приведён образец декомпилированного сгенерированного кода десериализатора, декомпилированный с помощью ILSpy.

Если количество узлов велико, то для поиска будет использоваться встроенный бинарный поиск.

Дополнительное замечание: это результат теста сериализации.

Method Mean Error Scaled Gen 0 Allocated
IntKey 84.11 ns NA 1.00 0.0094 40 B
StringKey 126.75 ns NA 1.51 0.0341 144 B
Typeless_IntKey 183.31 ns NA 2.18 0.0265 112 B
Typeless_StringKey 193.95 ns NA 2.31 0.0513 216 B
MsgPackCliMap 967.68 ns NA 11.51 0.1297 552 B
MsgPackCliArray 284.20 ns NA 3.38 0.1006 424 B
ProtobufNet 176.43 ns NA 2.10 0.0665 280 B
Hyperion 280.14 ns NA 3.33 0.1674 704 B
ZeroFormatter 149.95 ns NA 1.78 0.1009 424 B
JsonNetString 1,432.55 ns NA 17.03 0.4616 1944 B
JsonNetStreamWriter 1,775.72 ns NA 21.11 1.5526 6522 B
JilString 547.51 ns NA 6.51 0.3481 1464 B
JilStreamWriter 778.78 ns NA 9.26 1.4448 6066 B

Конечно, IntKey самый быстрый, но StringKey также показывает хорошие результаты.

String interning

Формат msgpack не предусматривает повторного использования строк в потоке данных. Это естественным образом приводит к тому, что десериализатор создаёт новый объект string для каждой встреченной строки, даже если она равна другой ранее встреченной строке.

Когда десериализуются данные, которые могут содержать одни и те же строки повторно, может быть полезно, чтобы десериализатор потратил немного больше времени на проверку того, видел ли он данную строку раньше, и повторно использовал её, если видел.

Чтобы включить интернирование строк для всех строковых значений, используйте преобразователь, который указывает StringInterningFormatter перед любым из стандартных, например так:

var options = MessagePackSerializerOptions.Standard.WithResolver(
    CompositeResolver.Create(
        new IMessagePackFormatter[] { new StringInterningFormatter() },
        new IFormatterResolver[] { StandardResolver.Instance })); **MessagePackSerializer.Deserialize<ClassOfStrings>(data, options)**

Если вы знаете, какие поля определённого типа могут содержать повторяющиеся строки, вы можете применить форматировщик интернирования строк только к этим полям, чтобы десериализатор оплачивал проверку интернированной строки только там, где это наиболее важно.

Обратите внимание, что этот метод требует класса `[MessagePackObject]` или `[DataContract]`.

**```**
**[MessagePackObject]**
**public class ClassOfStrings**
**{**
    **[Key(0)]**
    **[MessagePackFormatter(typeof(StringInterningFormatter))]**
    **public string InternedString { get; set; }**

    **[Key(1)]**
    **public string OrdinaryString { get; set; }**
**}**
**```**

Если вы пишете собственный форматировщик для какого-либо типа, который содержит строки, вы также можете напрямую вызвать `StringInterningFormatter` из своего форматировщика для этих строк.

## LZ4 Compression

MessagePack  это быстрый и компактный формат, но он не обеспечивает сжатие. LZ4  это чрезвычайно быстрый алгоритм сжатия, и его использование с MessagePack для C# может обеспечить чрезвычайно высокую производительность, а также чрезвычайно компактные размеры двоичных файлов!

В MessagePack для C# есть встроенная поддержка LZ4. Вы можете активировать её, используя изменённый объект параметров и передавая его в API следующим образом:

**```**
**var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray);**
**MessagePackSerializer.Serialize(obj, lz4Options);**
**```**

`MessagePackCompression` имеет два режима: `Lz4Block` и `Lz4BlockArray`. Ни один из них не является простым двоичным сжатием LZ4, а представляет собой специальное сжатие, интегрированное в конвейер сериализации, использующее код `ext` MessagePack (`Lz4BlockArray (98)` или `Lz4Block (99)`). Поэтому оно не совместимо со сжатием, предлагаемым на других языках.

`Lz4Block` сжимает всю последовательность MessagePack как один блок LZ4. Это простое сжатие, которое обеспечивает наилучшее соотношение сжатия за счёт копирования всей последовательности при необходимости для получения непрерывной памяти.

`Lz4BlockArray` сжимает всю последовательность MessagePack в виде массива блоков LZ4. Сжатые/распакованные блоки разбиваются на части и поэтому не попадают в кучу больших объектов GC, но коэффициент сжатия немного хуже.

Мы рекомендуем использовать `Lz4BlockArray` по умолчанию при использовании сжатия. Для совместимости с MessagePack v1.x используйте `Lz4Block`.

Независимо от того, какой вариант LZ4 установлен при десериализации, оба метода можно десериализовать. Например, когда используется опция `Lz4BlockArray`, можно десериализовать двоичные данные, использующие как `Lz4Block`, так и `Lz4BlockArray`. Их нельзя распаковать и, следовательно, десериализовать, если для параметра сжатия установлено значение `None`.

### Атрибуция

Поддержка сжатия LZ4 использует код Milosz Krajewski `lz4net` с некоторыми изменениями.

## Сравнение с protobuf, JSON, ZeroFormatter

protobuf-net  важная широко используемая библиотека двоичного формата в .NET. Мне нравится protobuf-net, и я уважаю их великолепную работу. Но когда вы используете protobuf-net в качестве формата сериализации общего назначения, вы можете столкнуться с раздражающей проблемой.

```csharp
[ProtoContract]
public class Parent
{
    [ProtoMember(1)]
    public int Primitive { get; set; }
    [ProtoMember(2)]
    public Child Prop { get; set; }
    [ProtoMember(3)]
    public int[] Array { get; set; }
}

[ProtoContract]
public class Child
{
    [ProtoMember(1)]
    public int Number { get; set; }
}

using (var ms = new MemoryStream())
{
    // serialize null.
    ProtoBuf.Serializer.Serialize<Parent>(ms, null);

    ms.Position = 0;
    var result = ProtoBuf.Serializer.Deserialize<Parent>(ms);

    Console.WriteLine(result != null); // True, not null. but all property are zero formatted.
    Console.WriteLine(result.Primitive); // 0
    Console.WriteLine(result.Prop); // null
    Console.WriteLine(result.Array); // null
}

using (var ms = new MemoryStream())
{
    // serialize empty array.
    ProtoBuf.Serializer.Serialize<Parent>(ms, new Parent { Array = System.Array.Empty<int>() });

``` **protobuf(-net) не может корректно обрабатывать null и пустую коллекцию, потому что в protobuf нет представления null (см. этот ответ SO от автора protobuf-net).**

**Система типов MessagePack может правильно сериализовать всю систему типов C#.** Это веская причина рекомендовать MessagePack вместо protobuf.

У Protocol Buffers хорошая поддержка IDL и gRPC. Если вы хотите использовать IDL, я рекомендую Google.Protobuf вместо MessagePack.

JSON  хороший универсальный формат. Он прост, удобочитаем и достаточно хорошо определён. Utf8Json, который я также создал, использует ту же архитектуру, что и MessagePack для C#, и максимально избегает затрат на кодирование/декодирование, как и эта библиотека. Если вы хотите узнать больше о двоичных и текстовых форматах, см. раздел «Какой сериализатор следует использовать» на странице Utf8Json.

ZeroFormatter похож на FlatBuffers, но специализирован для C#. Десериализация выполняется бесконечно быстро, но размер получаемого двоичного файла больше. Алгоритм кэширования ZeroFormatter требует дополнительной памяти.

Для многих распространённых случаев использования MessagePack для C# будет лучшим выбором.

## Советы по достижению максимальной производительности при использовании MessagePack для C#

MessagePack для C# по умолчанию обеспечивает максимальную производительность. Однако есть некоторые опции, которые жертвуют производительностью ради удобства.

### Используйте индексированные ключи вместо строковых ключей (Contractless)

В разделе «Производительность десериализации для разных опций» приведены результаты сравнения производительности индексированных ключей (IntKey) и строковых ключей (StringKey). Индексированные ключи сериализуют граф объектов как массив MessagePack. Строковые ключи сериализует граф объектов как карту MessagePack.

Например, этот тип сериализуется в:

```csharp
[MessagePackObject]
public class Person
{
    [Key(0)] или [Key("name")]
    public string Name { get; set;}
    [Key(1)] или [Key("age")]
    public int Age { get; set;}
}

new Person { Name = "foobar", Age = 999 }
  • IntKey: ["foobar", 999].
  • StringKey: {"name:"foobar","age":999}.

IntKey всегда быстр как при сериализации, так и при десериализации, поскольку ему не нужно обрабатывать и искать имена ключей, и он всегда имеет меньший двоичный размер.

StringKey часто является полезным, бесконтрактным, простым заменителем JSON, обеспечивает совместимость с другими языками с поддержкой MessagePack и менее подвержен ошибкам при управлении версиями. Но чтобы достичь максимальной производительности, используйте IntKey.

Создайте собственный пользовательский составной преобразователь

CompositeResolver.Create — это простой способ создания составных преобразователей. Но поиск формата имеет некоторые накладные расходы. Если вы создадите собственный преобразователь (или используете StaticCompositeResolver.Instance), вы сможете избежать этих накладных расходов. ``` return; }

        foreach (var resolver in Resolvers)
        {
            var f = resolver.GetFormatter<T>();
            if (f != null)
            {
                Formatter = f;
                return;
            }
        }
    }
}

}


> NOTE: Если вы создаёте библиотеку, рекомендуется использовать указанный выше пользовательский резолвер вместо `CompositeResolver.Create`. Также библиотеки не должны использовать `StaticCompositeResolver` — так как это глобальное состояние — чтобы избежать проблем с совместимостью.

### Использование нативных резолверов

По умолчанию MessagePack для C# сериализует GUID в виде строки. Это намного медленнее, чем собственный формат .NET GUID. То же самое относится и к Decimal. Если ваше приложение активно использует GUID или Decimal и вам не нужно беспокоиться о совместимости с другими языками, вы можете заменить их на собственные сериализаторы `NativeGuidResolver` и `NativeDecimalResolver` соответственно.

Также `DateTime` сериализуется с использованием формата метки времени MessagePack. Используя `NativeDateTimeResolver`, можно сохранить Kind и выполнить более быструю сериализацию.

### Будьте осторожны при копировании буферов

`MessagePackSerializer.Serialize` по умолчанию возвращает `byte[]`. Конечный `byte[]` копируется из внутреннего пула буферов. Это дополнительные затраты. Вы можете использовать `IBufferWriter<T>` или API `Stream`, чтобы писать прямо в буферы. Если вы хотите использовать пул буферов вне сериализатора, вам следует реализовать собственный `IBufferWriter<byte>` или использовать существующий, такой как [`Sequence<T>`](https://github.com/AArnott/Nerdbank.Streams/blob/master/doc/Sequence.md) из пакета [Nerdbank.Streams](https://nuget.org/packages/Nerdbank.Streams).

Во время десериализации `MessagePackSerializer.Deserialize(ReadOnlyMemory<byte> buffer)` лучше, чем перегрузка `Deserialize(Stream stream)`. Это связано с тем, что версия Stream API начинается с чтения данных, создания `ReadOnlySequence<byte>`, и только затем начинается десериализация.

### Выбор сжатия

Сжатие обычно эффективно, когда есть повторяющиеся данные. В MessagePack массивы, содержащие объекты с использованием строковых ключей (Contractless), могут быть эффективно сжаты, поскольку сжатие может быть применено ко многим повторяющимся именам свойств. Сжатие индексированных ключей не так эффективно, как строковые ключи, но индексированные ключи изначально меньше.

Вот некоторые примеры данных эталонных тестов производительности;

| Сериализатор | Среднее значение | Размер данных |
| ----------- | --------------:| ------------:|
| IntKey      | 2,941 мкс       | 469,00 Б     |
| IntKey(Lz4)  | 3,449 мкс       | 451,00 Б     |
| StringKey   | 4,340 мкс       | 1023,00 Б    |
| StringKey(Lz4)| 5,469 мкс       | 868,00 Б     |

`IntKey(Lz4)` не так хорошо сжимается, но производительность всё равно несколько ухудшается. С другой стороны, от `StringKey` можно ожидать достаточного влияния на размер двоичного файла. Однако это всего лишь пример. Сжатие может быть весьма эффективным в зависимости от данных или иметь небольшой эффект, кроме замедления работы вашей программы. Есть также случаи, когда хорошо сжимаемые данные существуют в значениях (например, длинные строки, например, содержащие HTML-данные со многими повторяющимися HTML-тегами). Важно проверять фактическое влияние сжатия в каждом конкретном случае.

## Расширения

MessagePack для C# имеет точки расширения, которые позволяют обеспечить оптимальную поддержку сериализации для пользовательских типов. Существуют официальные пакеты поддержки расширений.

```ps1
Install-Package MessagePack.ReactiveProperty
Install-Package MessagePack.UnityShims
Install-Package MessagePack.AspNetCoreMvcFormatter

Пакет MessagePack.ReactiveProperty добавляет поддержку типов библиотеки ReactiveProperty. Он добавляет поддержку сериализации ReactiveProperty<>, IReactiveProperty<>, IReadOnlyReactiveProperty<>, ReactiveCollection<>, Unit. Это полезно для сохранения состояния модели представления.

Пакет MessagePack.UnityShims предоставляет прокладки для стандартных структур Unity (Vector2, Vector3, Vector4, ...). Кватернион, цвет, границы, прямоугольник, кривая анимации, ключевой кадр, матрица 4x4, градиент, цвет 32, смещение прямоугольника, маска слоя, вектор 2 int, вектор 3 int, диапазон int, прямоугольник int, границы int) и соответствующие средства форматирования. Это может обеспечить надлежащую связь между серверами и клиентами Unity.

После установки необходимо включить пакеты расширений, создав составные преобразователи. Вот пример того, как можно включить все расширения.

// Установить расширения для стандартного преобразователя.
var resolver = MessagePack.Resolvers.CompositeResolver.Create(
    // сначала включить пакеты расширения
    ReactivePropertyResolver.Instance,
    MessagePack.Unity.Extension.UnityBlitResolver.Instance,
    MessagePack.Unity.UnityResolver.Instance,

    // наконец использовать стандартный (по умолчанию) преобразователь
    StandardResolver.Instance
);
var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);

// Передавать параметры каждый раз или установить по умолчанию
MessagePackSerializer.DefaultOptions = options;

Для получения подробной информации см.: Раздел «Точки расширения».

MessagePack.AspNetCoreMvcFormatter — это дополнение для сериализации ASP.NET Core MVC, которое повышает производительность. Это образец конфигурации.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddMvcOptions(option =>
    {
        option.OutputFormatters.Clear();
        option.OutputFormatters.Add(new MessagePackOutputFormatter(ContractlessStandardResolver.Options));
        option.InputFormatters.Clear();
        option.InputFormatters.Add(new MessagePackInputFormatter(ContractlessStandardResolver.Options));
    });
}

Другие авторы также создают пакеты расширений.

  • MagicOnion — gRPC на основе HTTP/2 RPC Streaming Framework.
  • MasterMemory — встроенная база данных документов только для чтения в памяти.

Вы можете создавать собственные сериализаторы расширений или интегрироваться с фреймворками. Давайте создадим и поделимся!

Экспериментальные функции

В MessagePack для C# есть экспериментальные функции, которые предоставляют вам очень эффективные средства форматирования. Существует официальный пакет.

Install-Package MessagePack.Experimental

Подробную информацию см.: Experimental.md.

API

Высокоуровневый API (MessagePackSerializer)

Класс MessagePackSerializer — это точка входа в MessagePack для C#. Статические методы составляют основной API MessagePack для C#.

API Описание
Serialize<T> Сериализует граф объектов в двоичный блок MessagePack. Доступен асинхронный вариант для потока. Доступны не универсальные перегрузки.
Deserialize<T> Десериализует двоичный файл MessagePack в граф объектов. Доступен асинхронный вариант для потока. Доступны не универсальные перегрузки.
SerializeToJson Сериализация графа объектов, совместимого с MessagePack, в JSON вместо MessagePack. Полезно для отладки.
ConvertToJson Преобразование двоичного файла MessagePack в JSON. Полезно для отладки.
ConvertFromJson Преобразовать JSON в двоичный файл MessagePack.

Класс MessagePackSerializer.Typeless предлагает большинство тех же API, что и выше, но удаляет все аргументы типа из API, заставляя сериализацию включать полное имя типа корневого объекта. Он использует TypelessContractlessStandardResolver. Считайте результат специфичным для .NET двоичным файлом MessagePack, который не готов к использованию. Совместимость с десериализаторами MessagePack в других средах выполнения.

MessagePack для C# принципиально сериализует с использованием IBufferWriter<byte> и десериализует, используя ReadOnlySequence<byte> или Memory<byte>. Предоставляются перегрузки методов для удобного использования с общими типами буферов и классом .NET Stream, но некоторые из этих перегрузок требуют копирования буферов один раз и поэтому имеют определённые накладные расходы.

Высокоуровневый API внутренне использует пул памяти, чтобы избежать ненужного выделения памяти. Если размер результата меньше 64 КБ, он выделяет память GC только для возвращаемых байтов.

Каждый метод сериализации/десериализации принимает необязательный параметр MessagePackSerializerOptions, который можно использовать для указания пользовательского IFormatterResolver или для активации поддержки сжатия LZ4.

Несколько структур MessagePack на одном Stream

Чтобы десериализовать Stream, содержащий несколько последовательных структур данных MessagePack, можно использовать класс MessagePackStreamReader для эффективного определения ReadOnlySequence<byte> для каждой структуры данных и её десериализации. Например:

static async Task<List<T>> DeserializeListFromStreamAsync<T>(Stream stream, CancellationToken cancellationToken)
{
    var dataStructures = new List<T>();
    using (var streamReader = new MessagePackStreamReader(stream))
    {
        while (await streamReader.ReadAsync(cancellationToken) is ReadOnlySequence<byte> msgpack)
        {
            dataStructures.Add(MessagePackSerializer.Deserialize<T>(msgpack, cancellationToken: cancellationToken));
        }
    }

    return dataStructures;
}

Низкоуровневый API (IMessagePackFormatter<T>)

Интерфейс IMessagePackFormatter<T> отвечает за сериализацию уникального типа. Например, Int32Formatter : IMessagePackFormatter<Int32> представляет сериализатор Int32 MessagePack.

public interface IMessagePackFormatter<T>
{
    void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options);
    T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options);
}

Многие встроенные форматы находятся в MessagePack.Formatters. Ваши пользовательские типы обычно автоматически поддерживаются встроенными преобразователями типов, которые генерируют новые типы IMessagePackFormatter<T> на лету с помощью динамического создания кода. См. нашу поддержку генерации кода AOT для платформ, которые не поддерживают это.

Однако некоторые типы — особенно те, что предоставляются сторонними библиотеками или самой средой выполнения — не могут быть соответствующим образом аннотированы, и бесконтрактная сериализация приведёт к неэффективным или даже неправильным результатам. Чтобы лучше контролировать сериализацию таких пользовательских типов, напишите собственную реализацию IMessagePackFormatter<T>.

Вот пример такой реализации пользовательского формата. Обратите внимание на использование примитивного API, который описан в следующем разделе.

/// <summary>Сериализует <see cref="FileInfo" /> по его полному пути в виде строки.</summary>
public class FileInfoFormatter : IMessagePackFormatter<FileInfo>
{
    public void Serialize(
      ref MessagePackWriter writer, FileInfo value, MessagePackSerializerOptions options)
    {
        if (value == null)
        {
            writer.WriteNil();
            return;
        }

        writer.WriteString(value.FullName);
    }

    public FileInfo Deserialize(
      ref MessagePackReader reader, MessagePackSerializerOptions options)
    {
        if (reader.TryReadNil())
        {
            return null;
        }

        options.Security.DepthStep(ref reader);

        var path = reader.ReadString();

        reader.Depth--;
        return new FileInfo(path);
    }
}

Операторы DepthStep и Depth-- обеспечивают уровень безопасности при десериализации ненадёжных данных, которые в противном случае могли бы выполнить атаку типа «отказ в обслуживании», отправив данные MessagePack, которые десериализовались бы в очень глубокий граф объектов, приводящий к StackOverflowException, что привело бы к сбою процесса. Эта пара операторов должна окружать большую часть любого IMessagePackFormatter<T>.Deserialize. Важно: Форматировщик пакетов сообщений должен читать или записывать ровно одну структуру данных.

В приведённом выше примере мы просто читаем/записываем строку. Если у вас есть более одного элемента для вывода, вы должны предварить его заголовком карты или массива. При десериализации вы должны прочитать всю карту/массив. Например:

public class MySpecialObjectFormatter : IMessagePackFormatter<MySpecialObject>
{
    public void Serialize(
      ref MessagePackWriter writer, MySpecialObject value, MessagePackSerializerOptions options)
    {
        if (value == null)
        {
            writer.WriteNil();
            return;
        }

        writer.WriteArrayHeader(2);
        writer.WriteString(value.FullName);
        writer.WriteString(value.Age);
    }

    public MySpecialObject Deserialize(
      ref MessagePackReader reader, MessagePackSerializerOptions options)
    {
        if (reader.TryReadNil())
        {
            return null;
        }

        options.Security.DepthStep(ref reader);

        string fullName = null;
        int age = 0;

        // Перебираем *все* элементы массива независимо от того, сколько мы ожидаем,
        // поскольку если мы сериализуем более старую/новую версию этого объекта,
        // количество сериализованных элементов может варьироваться, но контракт форматировщика
        // заключается в том, что должна быть прочитана ровно одна структура данных, несмотря ни на что.
        // В качестве альтернативы мы могли бы проверить, соответствует ли размер массива/карты ожидаемому,
        // и выдать ошибку, если это не так.
        int count = reader.ReadArrayHeader();
        for (int i = 0; i < count; i++)
        {
            switch (i)
            {
                case 0:
                    fullName = reader.ReadString();
                    break;
                case 1:
                    age = reader.ReadInt32();
                    break;
                default:
                    reader.Skip();
                    break;
            }
        }

        reader.Depth--;
        return new MySpecialObject(fullName, age);
    }
}

Ваши пользовательские форматировщики должны быть доступны через некоторый IFormatterResolver. Подробнее читайте в нашем разделе резолверы.

Вы можете увидеть множество других примеров из встроенных форматировщиков.

Примитивный API (MessagePackWriter, MessagePackReader)

Структуры MessagePackWriter и MessagePackReader составляют самый низкий уровень API. Они читают и записывают примитивные типы, определённые в спецификации MessagePack.

MessagePackReader

MessagePackReader может эффективно читать из ReadOnlyMemory<byte> или ReadOnlySequence<byte> без каких-либо выделений, за исключением выделения новой строки, как требуется методом ReadString(). Все остальные методы возвращают либо структуры значений, либо срезы ReadOnlySequence<byte> для расширений/массивов. Чтение непосредственно из ReadOnlySequence<byte> означает, что читатель может напрямую использовать некоторые современные высокопроизводительные API, такие как PipeReader.

Метод Описание
Skip Продвигает позицию читателя за текущее значение. Если значение сложное (например, карта, массив), вся структура пропускается.
Read* Чтение и возврат значения, тип которого назван именем метода, из текущей позиции чтения. Выбрасывает, если ожидаемый тип не совпадает с фактическим типом. При чтении чисел тип не обязательно должен точно соответствовать двоично заданному типу. Численное значение будет преобразовано в желаемый тип или выброшено, если целочисленный тип слишком мал для большого значения.
TryReadNil Продвигается за текущее значение, если текущее значение равно nil, и возвращает true; в противном случае оставляет позицию читателя неизменной и возвращает false.
ReadBytes Возвращает срез входной последовательности, представляющий содержимое byte[], и продвигает читателя.
ReadStringSequence Возвращает срез входной последовательности, представляющий содержимое строки string, без её декодирования, и продвигает читателя.
Clone Создаёт новый MessagePackReader с указанным вводом. MessagePackReader

MessagePackReader может автоматически интерпретировать как старую, так и новую спецификацию MessagePack.

Метод Описание
CreatePeekReader Создаёт новый считыватель с той же позицией, что и текущий, позволяя вызывающей стороне «читать вперёд», не влияя на позицию исходного считывателя.
NextCode Считывает низкоуровневый байт MessagePack, который описывает тип следующего значения. Не перемещает считыватель. См. Формат первого байта MessagePack. Его статический класс имеет служебные методы ToMessagePackType и ToFormatName. MessagePackRange означает диапазон формата MessagePack от минимального до максимального фиксированного значения.
NextMessagePackType Описывает значение NextCode как категорию более высокого уровня. Не перемещает считыватель. См. Спецификация MessagePack исходных типов.
(другие) Другие методы и свойства, описанные в файле комментариев .xml и Intellisense.

MessagePackReader способен автоматически интерпретировать обе старые и новые спецификации MessagePack.

MessagePackWriter

MessagePackWriter записывает данные в указанный экземпляр IBufferWriter. Существует несколько распространённых реализаций этого, позволяющих избежать выделения памяти и минимизировать копирование буфера при записи непосредственно в несколько API ввода-вывода, включая PipeWriter.

MessagePackWriter по умолчанию записывает новую спецификацию MessagePack, но может записывать MessagePack, совместимый со старой спецификацией, установив для свойства OldSpec значение true.

Метод Описание
Clone Создает новый MessagePackWriter с указанным базовым IBufferWriter и теми же настройками, что и исходный писатель.
Flush Записывает любые буферизованные байты в базовый IBufferWriter.
WriteNil Записывает эквивалент MessagePack для значения .NET null.
Write Записывает любое примитивное значение MessagePack в наиболее компактной форме. Имеет перегрузки для каждого примитивного типа, определённого спецификацией MessagePack.
WriteIntType Записывает целочисленное значение точно указанного типа MessagePack, даже если существует более компактный формат.
WriteMapHeader Представляет карту, указывая количество содержащихся в ней пар ключ=значение.
WriteArrayHeader Представляет массив, указывая количество элементов, которые он содержит.
WriteExtensionFormat Записывает полное содержимое значения расширения, включая длину, код типа и содержимое.
WriteExtensionFormatHeader Записывает только заголовок (длину и код типа) значения расширения.
WriteRaw Копирует указанные байты непосредственно в базовый IBufferWriter без какой-либо проверки.
(Другие) Другие методы и свойства, описанные в комментарии файла .xml и в Intellisense.

DateTime сериализуется в формат метки времени MessagePack, он сериализует/десериализует UTC и теряет информацию о Kind и требует, чтобы MessagePackWriter.OldSpec == false. Если вы используете NativeDateTimeResolver, значения DateTime будут сериализованы с использованием собственного представления Int64 .NET, которое сохраняет информацию о Kind, но может быть несовместимо с платформами, отличными от .NET.

Основная точка расширения (IFormatterResolver)

IFormatterResolver — это хранилище типизированных сериализаторов. API MessagePackSerializer принимает объект MessagePackSerializerOptions, который указывает используемый IFormatterResolver, позволяя настраивать сериализацию сложных типов.

Имя преобразователя Описание
BuiltinResolver Преобразователь встроенных примитивных и стандартных классов. Включает примитивные (int, bool, string...) и их обнуляемые типы, массивы и списки. А также некоторые дополнительные встроенные типы (Guid, Uri, BigInteger и т. д.).
StandardResolver Составной преобразователь. Он разрешает в следующем порядке: builtin -> атрибут -> динамическое перечисление -> динамический универсальный -> динамический союз -> динамический объект -> динамический объект fallback. Это значение по умолчанию для MessagePackSerializer.
ContractlessStandardResolver Составной преобразователь. StandardResolver (за исключением динамического объекта fallback) -> DynamicContractlessObjectResolver -> DynamicObjectTypeFallbackResolver. Это позволяет выполнять сериализацию без контрактов.
StandardResolverAllowPrivate То же, что и StandardResolver, но позволяет сериализовать/десериализовывать закрытые члены.
ContractlessStandardResolverAllowPrivate То же, что и ContractlessStandardResolver, но позволяет сериализовать/десериализовывать закрытые члены.
PrimitiveObjectResolver Решатель примитивных объектов MessagePack. Используется в качестве запасного варианта для типа object и поддерживает bool, char, sbyte, byte, short, int, long, ushort, uint, ulong, float, double, DateTime, string, byte[], ICollection, IDictionary.
DynamicObjectTypeFallbackResolver Сериализация используется для типа из типа object, десериализация использует PrimitiveObjectResolver.
AttributeFormatterResolver Получает форматировщик из атрибута [MessagePackFormatter].
CompositeResolver Объединяет несколько решателей и/или форматировщиков в упорядоченный список, позволяя повторно использовать и переопределять поведение существующих решателей и форматировщиков.
NativeDateTimeResolver Выполняет сериализацию с использованием собственного двоичного формата .NET DateTime. Сохраняет DateTime.Kind, который теряется при стандартной (отметка времени MessagePack) формате.
NativeGuidResolver Выполняет сериализацию с помощью собственного двоичного представления .NET Guid. Это быстрее, чем стандартное (строковое) представление.
NativeDecimalResolver Выполняет сериализацию с помощью собственного десятичного двоичного представления .NET. Это быстрее, чем стандартное (строковое) представление.
DynamicEnumResolver Решатель перечислений и их обнуляемых значений, сериализует их базовый тип. Использует динамическое создание кода, чтобы избежать упаковки и повысить производительность сериализации их имени.
DynamicEnumAsStringResolver Решает перечисления и их обнуляемые значения. Использует вызов отражения для разрешения обнуляемого значения в первый раз.
DynamicGenericResolver Решатель универсальных типов (Tuple<> , List<> , Dictionary<,>, Array и т.д.). Использует вызов отражения для разрешения универсального аргумента в первый раз.
DynamicUnionResolver Решатель интерфейса, помеченного атрибутом UnionAttribute. Использует динамическое создание кода для создания динамического форматировщика.
DynamicObjectResolver Решатель классов и структур, созданных с помощью MessagePackObjectAttribute. Использует динамическое создание кода для создания динамического форматировщика.
DynamicContractlessObjectResolver Решатель всех классов и структур. Не требует MessagePackObjectAttribute и сериализованного ключа в виде строки (так же, как помечено [MessagePackObject(true)]).
DynamicObjectResolverAllowPrivate То же самое, что DynamicObjectResolver, но разрешает сериализацию/десериализацию закрытых членов.
DynamicContractlessObjectResolverAllowPrivate То же самое, что DynamicContractlessObjectResolver, но разрешает сериализацию/десериализацию закрытых членов.
TypelessObjectResolver Используется для объекта, встраивает тип .NET в двоичный файл в формате ext(100), поэтому нет необходимости передавать тип при десериализации.
TypelessContractlessStandardResolver Составной решатель. Разрешает в следующем порядке: nativedatetime -> встроенный -> атрибут -> динамический enum -> динамический generic -> динамический union -> динамический объект -> динамический контрактless -> typeless. Это значение по умолчанию для MessagePackSerializer.Typeless.

Каждый экземпляр MessagePackSerializer принимает только один решатель. Для большинства графов объектов потребуется более одного для сериализации, поэтому часто требуется составить один решатель, состоящий из нескольких, и это можно сделать с помощью CompositeResolver, как показано ниже:

// Сделайте это один раз и сохраните для повторного использования.
var resolver = MessagePack.Resolvers.CompositeResolver.Create(
    // пользовательские типы решателя сначала
    ReactivePropertyResolver.Instance,
    MessagePack.Unity.Extension.UnityBlitResolver.Instance,
    MessagePack.Unity.UnityResolver.Instance,

    // наконец, используйте стандартный решатель
    StandardResolver.Instance
);
var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);

// Каждый раз, когда вы сериализуете/десериализуете, указывайте параметры:
byte[] msgpackBytes = MessagePackSerializer.Serialize(myObject, options);
T myObject2 =
``` **MessagePackSerializer.Deserialize<MyObject>(msgpackBytes, options)**

Резолвер можно установить по умолчанию с помощью `MessagePackSerializer.DefaultOptions = options`, но **ВНИМАНИЕ**:
При разработке приложения, где вы контролируете весь код, связанный с MessagePack, может быть безопасно полагаться на этот изменяемый статический элемент для управления поведением.
Для всех остальных библиотек или многоцелевых приложений, использующих `MessagePackSerializer`, вы должны явно указать `MessagePackSerializerOptions`, которые будут использоваться при каждом вызове метода, чтобы гарантировать, что ваш код ведёт себя так, как вы ожидаете, даже когда вы делитесь `AppDomain` или процессом с другими пользователями MessagePack, которые могут изменить это статическое свойство.

Вот пример использования `DynamicEnumAsStringResolver` с `DynamicContractlessObjectResolver` (это лёгкая настройка, подобная Json.NET).

```csharp
// composite same as StandardResolver
var resolver = MessagePack.Resolvers.CompositeResolver.Create(
    MessagePack.Resolvers.BuiltinResolver.Instance,
    MessagePack.Resolvers.AttributeFormatterResolver.Instance,

    // replace enum resolver
    MessagePack.Resolvers.DynamicEnumAsStringResolver.Instance,

    MessagePack.Resolvers.DynamicGenericResolver.Instance,
    MessagePack.Resolvers.DynamicUnionResolver.Instance,
    MessagePack.Resolvers.DynamicObjectResolver.Instance,

    MessagePack.Resolvers.PrimitiveObjectResolver.Instance,

    // final fallback(last priority)
    MessagePack.Resolvers.DynamicContractlessObjectResolver.Instance
);

Если вы хотите создать пакет расширения, вам следует написать и форматтер, и резолвер для более удобного использования. Вот пример резолвера:

public class SampleCustomResolver : IFormatterResolver
{
    // Резолвер должен быть одноэлементным.
    public static readonly IFormatterResolver Instance = new SampleCustomResolver();

    private SampleCustomResolver()
    {
    }

    // GetFormatter<T>'s get cost should be minimized so use type cache.
    public IMessagePackFormatter<T> GetFormatter<T>()
    {
        return FormatterCache<T>.Formatter;
    }

    private static class FormatterCache<T>
    {
        public static readonly IMessagePackFormatter<T> Formatter;

        // generic's static constructor should be minimized for reduce type generation size!
        // use outer helper method.
        static FormatterCache()
        {
            Formatter = (IMessagePackFormatter<T>)SampleCustomResolverGetFormatterHelper.GetFormatter(typeof(T));
        }
    }
}

internal static class SampleCustomResolverGetFormatterHelper
{
    // If type is concrete type, use type-formatter map
    static readonly Dictionary<Type, object> formatterMap = new Dictionary<Type, object>()
    {
        {typeof(FileInfo), new FileInfoFormatter()}
        // add more your own custom serializers.
    };

    internal static object GetFormatter(Type t)
    {
        object formatter;
        if (formatterMap.TryGetValue(t, out formatter))
        {
            return formatter;
        }

        // If type can not get, must return null for fallback mechanism.
        return null;
    }
}

MessagePackFormatterAttribute

MessagePackFormatterAttribute — это лёгкая точка расширения класса, структуры, интерфейса, перечисления и свойства/поля. Это похоже на Json.NET JsonConverterAttribute. Например, сериализовать личное поле, сериализовать x10 форматтер.

[MessagePackFormatter(typeof(CustomObjectFormatter))]
public class CustomObject
{
    string internalId;

    public CustomObject()
    {
        this.internalId = Guid.NewGuid().ToString();
    }

    class CustomObjectFormatter : IMessagePackFormatter<CustomObject>
    {
        public void Serialize(ref MessagePackWriter writer, CustomObject value, MessagePackSerializerOptions options)
        {
            options.Resolver.GetFormatterWithVerify<string>().Serialize(ref writer, value.internalId, options);
        }

        public CustomObject Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
        {
            var id =
``` **Текст запроса:**

options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); return new CustomObject { internalId = id }; } }

// per field, member

public class Int_x10Formatter : IMessagePackFormatter { public int Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { return reader.ReadInt32() * 10; }

public void Serialize(ref MessagePackWriter writer, int value, MessagePackSerializerOptions options)
{
    writer.WriteInt32(value * 10);
}

}

[MessagePackObject] public class MyClass { // You can attach custom formatter per member. [Key(0)] [MessagePackFormatter(typeof(Int_x10Formatter))] public int MyProperty1 { get; set; } }


**Перевод текста на русский язык:**

options.Resolver.GetFormatterWithVerify<строка>().Deserialize(ссылка на читатель, опции); вернуть новый объект CustomObject {internalId = идентификатор}; } }

// для каждого поля, члена

общественный класс Int_x10Formatter: IMessagePackFormatter<целое число> { общественное целое число Deserialize (ссылка на MessagePackReader читатель, MessagePackSerializerOptions опции) { вернуть читатель.ReadInt32 () * 10; }

общественная пустота Serialize (ссылка на MessagePackWriter писатель, целое значение, MessagePackSerializerOptions опции) { писатель.WriteInt32 (значение * 10); } }

[MessagePackObject] общественный класс MyClass { // Вы можете присоединить пользовательский форматтер к каждому члену. [Ключ (0)] [MessagePackFormatter (тип (Int_x10Formatter))] публичное целое число MyProperty1 {получить; установить;} }


У MessagePack для C# есть дополнительное небезопасное расширение. UnsafeBlitResolver — это специальный резолвер для чрезвычайно быстрой, но небезопасной сериализации/десериализации массивов структур.

x20 быстрее сериализация Vector3[], чем нативная JsonUtility. Если использовать UnsafeBlitResolver, то сериализация использует специальный формат (ext:typecode 30~39) для Vector2[], Vector3[], Quaternion[], Color[], Bounds[], Rect[]. Если использовать UnityBlitWithPrimitiveArrayResolver, он также поддерживает int[], float[], double[]. Эта специальная функция полезна для сериализации Mesh (много Vector3[]) или множества позиций трансформации.

Если вы хотите использовать небезопасный резолвер, зарегистрируйте UnityBlitResolver или UnityBlitWithPrimitiveArrayResolver.

Вот пример конфигурации.
```csharp
StaticCompositeResolver.Instance.Register(
    MessagePack.Unity.UnityResolver.Instance,
    MessagePack.Unity.Extension.UnityBlitWithPrimitiveArrayResolver.Instance,
    MessagePack.Resolvers.StandardResolver.Instance
);

var options = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance);
MessagePackSerializer.DefaultOptions = options;

Пакет NuGet MessagePack.UnityShims предназначен для поддержки сериализации на стороне сервера .NET для связи с Unity. Он включает прокладки для Vector3 и т. д., а также расширения безопасной/небезопасной сериализации.

Если вы хотите поделиться классом между Unity и сервером, вы можете использовать SharedProject или Reference as Link или глобальную ссылку (с LinkBase) и т.д. В любом случае вам нужно делиться на уровне исходного кода. Это пример структуры проекта с использованием глобальной ссылки (рекомендуется).

  • ServerProject(.NET 4.6/.NET Core/.NET Standard)
    • [<Compile Include="..\UnityProject\Assets\Scripts\Shared\**\*.cs" LinkBase="Shared" />]
    • [MessagePack]
    • [MessagePack.UnityShims]
  • UnityProject
    • [Concrete SharedCodes]
    • [MessagePack](не dll/NuGet, используйте исходный код MessagePack.Unity.unitypackage)

AOT Code Generation (поддержка Unity/Xamarin)

По умолчанию MessagePack для C# сериализует пользовательские объекты путём генерации IL на лету во время выполнения для создания пользовательских, высоконастроенных форматеров для каждого типа. Генерация кода имеет небольшие начальные затраты на производительность. Поскольку строгие среды AOT, такие как Xamarin и Unity IL2CPP, запрещают генерацию кода во время выполнения, MessagePack предоставляет способ запуска генератора кода заранее.

Примечание: при использовании Unity динамическая генерация кода работает только при таргетинге на .NET Framework 4.x + mono runtime. Для всех остальных целей Unity требуется AOT.

Если вы хотите избежать начальных затрат на динамическую генерацию или вам нужно работать с Xamarin или Unity, вам нужна генерация кода AOT. mpc (MessagePackCompiler) — это генератор кода MessagePack для C#. mpc использует Roslyn, чтобы анализировать исходный код.

Прежде всего, mpc требует .NET Core 3 Runtime. Самый простой способ получить и запустить mpc — использовать инструмент dotnet.

dotnet tool install --global MessagePack.Generator

Установка его в качестве локального инструмента позволяет вам включать инструменты и версии, которые вы используете в своей системе управления версиями. Выполните эти команды в корне вашего репозитория:

dotnet new tool-manifest
dotnet tool install MessagePack.Generator

Проверьте файл .config\dotnet-tools.json. На другом компьютере вы можете «восстановить» свой инструмент с помощью команды dotnet tool restore.

После установки инструмента просто вызовите его с помощью dotnet mpc в своём репозитории:

dotnet mpc --help

Кроме того, вы можете загрузить mpc со [страницы релизов][Releases], которая включает собственные двоичные файлы платформы (которым не требуется отдельная среда выполнения dotnet).

Usage: mpc [options...]

Options:
  -i, -input <String>                                Input path to MSBuild project
``` **Файл или каталог, содержащий исходные файлы Unity.** *(Обязательно)*

-o, -output <String> — путь к выходному файлу (.cs) или каталогу (генерируется несколько файлов). *(Обязательно)*  

-c, -conditionalSymbol <String> — символы условного компилятора, разделенные ','. *(По умолчанию: null)*  

-r, -resolverName <String> — задать имя резолвера. *(По умолчанию: GeneratedResolver)*  

-n, -namespace <String> — установить корневое имя пространства имен. *(По умолчанию: MessagePack)*  

-m, -useMapMode <Boolean> — принудительно использовать режим сериализации карты. *(По умолчанию: False)*  

-ms, -multipleIfDirectiveOutputSymbols <String> — генерировать файлы #if-- по символам, разделенным ','. *(По умолчанию: null)*

**MPC нацелен на код C# с аннотациями [MessagePackObject] или [Union].**

```cmd
// Простой пример:
dotnet mpc -i "..\src\Sandbox.Shared.csproj" -o "MessagePackGenerated.cs"

// Используйте режим симуляции DynamicContractlessObjectResolver
dotnet mpc -i "..\src\Sandbox.Shared.csproj" -o "MessagePackGenerated.cs" -m

По умолчанию MPC генерирует резолвер как MessagePack.Resolvers.GeneratedResolver и форматы как MessagePack.Formatters.*.

Вот полный пример кода для регистрации сгенерированного резолвера в Unity.

using MessagePack;
using MessagePack.Resolvers;
using UnityEngine;

public class Startup
{
    static bool serializerRegistered = false;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Initialize()
    {
        if (!serializerRegistered)
        {
            StaticCompositeResolver.Instance.Register(
                MessagePack.Resolvers.GeneratedResolver.Instance,
                MessagePack.Resolvers.StandardResolver.Instance
            );

            var option = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance);

            MessagePackSerializer.DefaultOptions = option;
            serializerRegistered = true;
        }
    }

#if UNITY_EDITOR


    [UnityEditor.InitializeOnLoadMethod]
    static void EditorInitialize()
    {
        Initialize();
    }

#endif
}

В Unity вы можете использовать окна MessagePack CodeGen в Windows -> MessagePack -> CodeGenerator.

Установите среду выполнения .NET Core, установите MPC (как инструмент .NET Core, как описано выше) и выполните dotnet mpc. В настоящее время этот инструмент является экспериментальным, поэтому, пожалуйста, поделитесь своим мнением.

В Xamarin вы можете установить пакет NuGet the MessagePack.MSBuild.Tasks в свои проекты, чтобы предварительно скомпилировать быстрый сериализационный код и запустить его в средах, где JIT-компиляция не разрешена.

RPC

MessagePack поддерживает MessagePack RPC, но работа над ним остановлена, и он не получил широкого распространения.

MagicOnion

Я создал основанную на gRPC потоковую структуру MessagePack HTTP/2 RPC под названием MagicOnion. Обычно gRPC взаимодействует с Protocol Buffers с помощью IDL. Но MagicOnion использует MessagePack для C# и не нуждается в IDL. При взаимодействии C# с C# бессхемный подход (или, скорее, классы C# в качестве схемы) лучше, чем использование IDL.

StreamJsonRpc

Библиотека StreamJsonRpc основана на JSON-RPC и включает в себя архитектуру подключаемых форматов и начиная с версии 2.3 поддерживает MessagePack (https://github.com/microsoft/vs-streamjsonrpc/blob/master/doc/extensibility.md#message-formatterss).

Как собрать

См. наше руководство для участников (CONTRIBUTING.md).

Информация об авторе

Йошифуми Каваи (a.k.a. neuecc) — разработчик программного обеспечения из Японии. Он является директором/техническим директором Grani, Inc. Grani — это компания по разработке мобильных игр в Японии, известная использованием C#. Он является обладателем звания Microsoft MVP за Visual C# с 2011 года. Он известен как создатель UniRx (реактивные расширения для Unity).

Блог: Релизс: https://github.com/neuecc/MessagePack-CSharp/releases

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Экстримально быстрый сериализатор MessagePack для C# (.NET, .NET Core, Unity, Xamarin). / msgpack.org[C#] Развернуть Свернуть
MIT
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/mirrors-MessagePack-CSharp.git
git@api.gitlife.ru:oschina-mirror/mirrors-MessagePack-CSharp.git
oschina-mirror
mirrors-MessagePack-CSharp
mirrors-MessagePack-CSharp
master