Переход с MessagePack версии 1.x на версию 2.x
MessagePack 2.0 содержит множество критических изменений по сравнению с версиями 1.x. Они включают в себя как двоичные, так и исходные критические изменения, что означает, что вам может потребоваться обновить исходный код, а также перекомпилировать его с версией 2.x.
Версия 1.x всё ещё будет поддерживаться для исправлений безопасности, но новые функции, как правило, будут предлагаться только в версиях 2.x.
Обновите ссылки на пакеты с версии 1.x, которую вы используете, до версии 2.x. Если ваш проект компилируется, то вы можете закончить работу. В противном случае просмотрите каждую ошибку компилятора. Некоторые распространённые ошибки перечислены ниже с предложенными исправлениями.
Если у вас есть приложение, которое имеет смесь потребителей MessagePack, и не все из них могут быть обновлены до версии v2.x сразу, вы можете предложить сборки MessagePack v1.x и v2.x вместе со своим приложением, чтобы каждый пользователь мог найти то, что ему нужно. Вот пример.
Новый класс MessagePackSerializerOptions
становится основным в этой библиотеке. Он инкапсулирует IFormatterResolver
, который раньше передавался сам по себе. Также он включает несколько других настроек, которые могут влиять на работу MessagePackSerializerOptions
или некоторых форматеров.
Поскольку этот новый класс обычно сохраняется в общедоступных статических свойствах, он является неизменяемым, чтобы обеспечить безопасное совместное использование. Каждое свойство Foo
в классе включает метод WithFoo
, который клонирует экземпляр и возвращает новый экземпляр с изменённым только одним свойством.
Чтобы поддержать этот новый класс опций и избежать ненужных выделений от методов копирования и изменения, многие популярные резолверы теперь предоставляют общедоступное статическое свойство Options
с предустановленным для себя резолвером. Например, вы можете использовать:
var msgpack = MessagePackSerializer.Serialize(objectGraph, StandardResolverAllowPrivate.Options);
var deserializedGraph = MessagePackSerializer.Deserialize<MyType>(msgpack, StandardResolverAllowPrivate.Options);
Если вы хотите объединить определённый резолвер с другими изменениями опций (например, включить сжатие LZ4), вы также можете это сделать:
var options = StandardResolverAllowPrivate.Options.WithCompression(MessagePackCompression.Lz4BlockArray);
var msgpack = MessagePackSerializer.Serialize(objectGraph, options);
var deserializedGraph = MessagePackSerializer.Deserialize<MyType>(msgpack, options);
Эквивалентный экземпляр опций можно создать вручную:
var options = MessagePackSerializerOptions.Standard
.WithCompression(MessagePackCompression.Lz4BlockArray)
.WithResolver(StandardResolverAllowPrivate.Instance);
Сериализация графов объектов в msgpack теперь основана на IBufferWriter<byte>
вместо ref byte[]
. Это позволяет сериализовать очень большие графы объектов без многократного выделения всё больших массивов и копирования ранее сериализованных байтов msgpack из меньшего буфера в больший. IBufferWriter<byte>
может направлять записанные байты msgpack непосредственно в канал, файл или куда угодно, позволяя вам также избегать копирования буфера в собственном коде.
IBufferWriter<byte>
всегда оборачивается новой структурой MessagePackWriter
.
Существует множество перегрузок метода Serialize
, которые в конечном итоге вызывают перегрузку, принимающую MessagePackWriter
.
Десериализация последовательностей msgpack теперь гораздо более гибкая. Вместо десериализации только из byte[]
или ArraySegment<byte>
, вы можете десериализоваться из любого экземпляра ReadOnlyMemory<byte>
или ReadOnlySequence<byte>
.
ReadOnlyMemory<byte>
похож на ArraySegment<byte>
, но более дружественен и может ссылаться на непрерывную память в любом месте, включая собственные указатели. Вы можете передать byte[]
или ArraySegment<byte>
везде, где ожидается ReadOnlyMemory<byte>
, и C# неявно приведёт их для вас (без какого-либо копирования буфера).
ReadOnlySequence<byte>
позволяет выполнять десериализацию из несмежно выделенной памяти, позволяя вам Deserialize очень большие последовательности Msgpack без риска OutOfMemoryException просто из-за невозможности найти большое количество свободной непрерывной памяти
Существует множество перегрузок метода Deserialize, которые в конечном итоге вызывают перегрузку, принимающую MessagePackReader.
Десериализация из Stream изменилась с версии 1.x до версии 2.0. Параметр readStrict был удалён, и в версии 2.x методы MessagePackSerializer.Deserialize{Async}(Stream) действуют так же, как если бы readStrict: false в версии 1.x. Это отлично работает и является предпочтительным API для использования, когда ожидается, что весь Stream будет содержать ровно одну структуру верхнего уровня messagepack, которую вы хотите десериализовать.
Из соображений производительности весь поток считывается в память перед началом десериализации. Если в потоке больше данных, чем структура messagepack для десериализации, десериализация проигнорирует лишние данные, но лишних данных больше не будет в потоке, чтобы их можно было прочитать позже.
Если поток доступен для поиска (то есть его свойство CanSeek возвращает true), то после завершения десериализации поток будет перемещён на первый байт после структуры данных messagepack, которая была десериализована. Это означает, что вы получите поток обратно, как и ожидали, но только после того, как вы заплатили за производительность «чтения» большего количества данных, чем было необходимо для десериализации.
Если поток не доступен для поиска (например, сетевой поток) или содержит несколько структур данных верхнего уровня messagepack подряд, MessagePack 2.0 добавляет новый, более эффективный способ чтения каждой структуры messagepack. Он аналогичен режиму readStrict: true версии 1.x, но гораздо более эффективен. Он представлен в виде нового класса MessagePackStreamReader и может быть легко использован следующим образом:
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;
}
Статическое свойство DefaultResolver было заменено статическим свойством DefaultOptions. Как и в случае с версией 1.x, в версии 2.x это статическое свойство влияет на то, как происходит сериализация, когда значение явно не указано при вызове одного из методов MessagePackSerializer.
ПРЕДУПРЕЖДЕНИЕ: При разработке простого приложения, где вы контролируете весь код, связанный с MessagePack, может быть безопасно полагаться на этот изменяемый статический элемент управления поведением. Для всех других библиотек или многоцелевых приложений, использующих MessagePackSerializer, вы должны явно указать MessagePackSerializerOptions для использования с каждым вызовом метода, чтобы гарантировать, что ваш код ведёт себя так, как вы ожидаете, даже при совместном использовании AppDomain или процесса с другими пользователями MessagePack, которые могут изменить это статическое свойство.
В версии 1.x неуниверсальные методы сериализации/десериализации были представлены во вложенном классе MessagePackSerializer.NonGeneric. В версии 2.x эти перегрузки перемещены в сам класс MessagePackSerializer.
Вложенный класс MessagePackSerializer.Typeless в версии 1.x остаётся в версии 2.x, но с изменённым набором перегрузок.
В версии 1.x класс MessagePackSerializer предоставлял методы как для сериализации графа объектов в JSON, так и для преобразования между msgpack и JSON. Эти два перевода сильно отличались друг от друга, но были всего лишь перегрузками друг друга. В версии 2.x эти методы были переименованы для ясности. Методы ConvertFromJson и ConvertToJson переводят между JSON и двоичным файлом msgpack. Метод SerializeToJson переводит граф объектов в JSON.
Класс LZ4MessagePackSerializer был удалён. Вместо этого используйте MessagePackSerializer и передайте LoadTypeCustomizedOptions(MessagePackSerializerOptions copyFrom)
: base(copyFrom) { }
internal LoadTypeCustomizedOptions(IFormatterResolver resolver)
: base(resolver) { }
public override Type LoadType(string typeName)
Type type = base.LoadType(typeName);
if (type == null)
{
// custom logic here
}
return type;
}
Затем можно создать экземпляр этого типа параметров и передать его десериализатору:
```cs
var options = new LoadTypeCustomizedOptions(MessagePackSerializerOptions.Standard);
T value = MessagePackSerializer.Deserialize<T>(sequence, options);
Если вы написали пользовательскую реализацию IMessagePackFormatter<T>
, вам придётся адаптироваться к изменениям интерфейса и API, используемым для реализации такого класса.
Интерфейс был изменён следующим образом:
public interface IMessagePackFormatter<T> : IMessagePackFormatter
{
- int Serialize(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver);
+ void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options);
- T Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize);
+ T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options);
}
Обратите внимание на более простую сигнатуру метода для каждого метода.
Вам больше не нужно иметь дело с необработанными массивами и смещениями.
Статический класс MessagePackBinary
из версии 1.x, который форматировщик использовал для записи кодов msgpack, заменён на MessagePackWriter
и MessagePackReader
.
Эти две структуры включают API для записи и чтения msgpack и управляют базовыми буферами, поэтому вам больше не нужно это делать.
Рассмотрим следующий форматировщик версии 1.x для типа Int16
:
class NullableInt16Formatter : IMessagePackFormatter<Int16?>
{
public int Serialize(ref byte[] bytes, int offset, Int16? value, IFormatterResolver formatterResolver)
{
if (value == null)
{
return MessagePackBinary.WriteNil(ref bytes, offset);
}
else
{
return MessagePackBinary.WriteInt16(ref bytes, offset, value.Value);
}
}
public Int16? Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize)
{
if (MessagePackBinary.IsNil(bytes, offset))
{
readSize = 1;
return null;
}
else
{
return MessagePackBinary.ReadInt16(bytes, offset, out readSize);
}
}
}
После миграции на версию 2.x он выглядит так:
class NullableInt16Formatter : IMessagePackFormatter<Int16?>
{
public void Serialize(ref MessagePackWriter writer, Int16? value, MessagePackSerializerOptions options)
{
if (value == null)
{
writer.WriteNil();
}
else
{
writer.Write(value.Value);
}
}
public Int16? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
if (reader.TryReadNil())
{
return default;
}
else
{
return reader.ReadInt16();
}
}
}
Обратите внимание, что структура очень похожа, но массивы и смещения больше не нужны. Базовый формат msgpack не изменился, позволяя обновить код до версии 2.x при сохранении совместимости с файлом или сетевой стороной, которая использует MessagePack версии 1.x.
При написании целых чисел шаблон имён методов изменился таким образом, что хотя ваш код v1.x->v2.0 будет компилироваться, он может создавать немного другой (и менее эффективный) двоичный файл msgpack по сравнению с предыдущим. Вот таблица перевода:
v1.x | v2.x |
---|---|
MessagePackBinary.WriteMapHeaderForceMap32Block |
(удалено) |
MessagePackBinary.WriteArrayHeaderForceArray32Block |
(удалено) |
MessagePackBinary.WriteByteForceByteBlock |
MessagePackWriter.WriteUInt8(byte) |
MessagePackBinary.WriteSByteForceSByteBlock |
MessagePackWriter.WriteInt8(sbyte) |
------------------------------------------ | -------------------------------------- |
MessagePackBinary.WriteInt64ForceInt64Block | MessagePackWriter.WriteInt64(long) |
MessagePackBinary.MessagePackBinary.WriteInt32ForceInt32Block | MessagePackWriter.WriteInt32(int) |
MessagePackBinary.WriteUInt16ForceUInt16Block | MessagePackWriter.WriteUInt16(ushort) |
MessagePackBinary.WriteUInt32ForceUInt32Block | MessagePackWriter.WriteUInt32(uint) |
MessagePackBinary.WriteUInt64ForceUInt64Block | MessagePackWriter.WriteUInt64(ulong) |
MessagePackBinary.WriteStringForceStr32Block | (удалено) |
MessagePackBinary.WriteExtensionFormatHeaderForceExt32Block | (удалено) |
MessagePackBinary.WriteMapHeader | MessagePackWriter.WriteMapHeader |
MessagePackBinary.WriteArrayHeader | MessagePackWriter.WriteArrayHeader |
MessagePackBinary.WriteByte | MessagePackWriter.Write(byte) |
MessagePackBinary.WriteBytes | MessagePackWriter.Write(byte[]) |
MessagePackBinary.WriteSByte | MessagePackWriter.Write(sbyte) |
MessagePackBinary.WriteSingle | MessagePackWriter.Write(float) |
MessagePackBinary.WriteDouble | MessagePackWriter.Write(double) |
MessagePackBinary.WriteInt16 | MessagePackWriter.Write(short) |
MessagePackBinary.WriteInt32 | MessagePackWriter.Write(int) |
MessagePackBinary.WriteInt64 | MessagePackWriter.Write(long) |
MessagePackBinary.WriteUInt16 | MessagePackWriter.Write(ushort) |
MessagePackBinary.WriteUInt32 | MessagePackWriter.Write(uint) |
MessagePackBinary.WriteUInt64 | MessagePackWriter.Write(ulong) |
MessagePackBinary.WriteChar | MessagePackWriter.Write(char) |
MessagePackBinary.WriteStringBytes | MessagePackWriter.WriteString(ReadOnlySpan) |
MessagePackBinary.WriteString | MessagePackWriter.Write(string) |
MessagePackBinary.WriteExtensionFormatHeader | MessagePackWriter.WriteExtensionFormatHeader |
MessagePackBinary.WriteExtensionFormat | MessagePackWriter.WriteExtensionFormat |
MessagePackBinary.WriteDateTime | MessagePackWriter.Write(DateTime) (notes) |
Суть в том, что обычно вы можете просто вызывать MessagePackWriter.Write(*)
для примитивных типов, и будет записан наиболее эффективный двоичный файл msgpack. Вы должны вызывать явные методы WriteX(x)
, только если вам нужно принудительно записать определённый (фиксированный) формат значения.
Что касается методов чтения целых чисел, они гораздо более взаимозаменяемы, чем в версии v1.x. Вы можете вызвать любой метод ReadInt*
или ReadUInt*
, и он успешно прочитает целочисленное значение и подгонит его под желаемый тип возвращаемого значения, если значение не переполняется. Например, вы можете вызвать Write(byte)
, а позже прочитать значение с помощью ReadInt32()
. Можно даже вызвать Write(long)
, а затем прочитать его с помощью ReadByte()
, и это сработает, пока фактическое значение помещается в byte
. Если целочисленное значение превышает максимальное или минимальное значение, которое может быть сохранено требуемым типом возвращаемого значения, генерируется исключение OverflowException
.
При записи DateTime
версия v1.x всегда вызывала DateTime.ToUniversalTime()
перед сериализацией значения. В версии v2.x мы вызываем этот метод, только если DateTime.Kind == DateTimeKind.Local
. Это означает, что если вы записывали DateTimeKind.Unspecified
, то сериализованное значение больше не будет изменяться при необоснованном предположении, что базовое значение было Local
. Вы должны явно указывать DateTimeKind
для всех значений DateTime
. При обновлении до MessagePack v2.x это критическое изменение, если ваши значения Unspecified
фактически представляли местное время и нуждались в преобразовании.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )