MessagePack for C#

快速序列化组件MessagePack介绍
 
简介

MessagePack for C#(MessagePack-CSharp)是用于C#的极速MessagePack序列化程序,比MsgPack-Cli快10倍,与其他所有C#序列化程序相比,具有最好的性能。 MessagePack for C#具有内置的LZ4压缩功能,可以实现超快速序列化和二进制占用空间小。 性能永远是重要的! 可用于游戏,分布式计算,微服务,数据存储到Redis等。支持.NET, .NET Core, Unity, Xamarin。

序列化 反序列化 MessagePack for C#_干货

从上图我们看出MessagePack for C#在性能测试中是最好的,这里解释一下第三个MsgPack-Cli是MessagePack官方实现的。第一和第二都是MessagePack for C#,第一项相比第二项具有稍快一点的序列化和反序列化速度,但是第二项采用了L4压缩功能,显著的减少了二进制的大小。在实际使用中推荐使用L4压缩功能。

使用

该组件已经发布在Nuget,使用命令加入项目。

Install-Package MessagePack

分析器

Install-Package MessagePackAnalyzer

扩展

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

Unity在此处下载 https://github.com/neuecc/MessagePack-CSharp/releases

快速开始

定义一个类添加[MessagePackObject]特性,公共成员(属性或者字段)添加[Key]特性,调用MessagePackSerializer.Serialize<T>/Deserialize<T>进行序列化和反序列化,ToJson可以帮我们转储二进制为json格式。

// 标记 MessagePackObjectAttribute
[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; }

    // 公共成员中不序列化目标,标记IgnoreMemberAttribute
    [IgnoreMember]
    public string FullName { get { return FirstName + LastName; } }
}
class Program
{
    static void Main(string[] args)
    {
        var mc = new MyClass
        {
            Age = 99,
            FirstName = "hoge",
            LastName = "huga",
        };

        // 序列化
        var bytes = MessagePackSerializer.Serialize(mc);
        //反序列化
        var mc2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

        // 你可以将msgpack二进制转储为可读的json。
        // 在默认情况下,MeesagePack for C#减少了属性名称信息。
        // [99,"hoge","huga"]
        var json = MessagePackSerializer.ToJson(bytes);
        Console.WriteLine(json);

        Console.ReadKey();

    }
}

序列化索引将会影响该信息在序列化数据中的位置

默认情况下特性是必须的,但是我们有方法进行改变,让它变为不是必须的,详情请看后面。

分析器

MessagePackAnalyzer 可以帮助我们定义对象. 如果不符合规则,那么特性, 程序集等可以被检测到,如果我们编译就会出现编译错误。

序列化 反序列化 MessagePack for C#_干货_02

如果要允许特定类型(例如,注册自定义类型时),请将MessagePackAnalyzer.json放在项目根目录下,并将生成操作设置为AdditionalFiles(其他文件)。

序列化 反序列化 MessagePack for C#_干货_03

这是MessagePackAnalyzer.json内容的一个示例。

[ "MyNamespace.FooClass", "MyNameSpace.BarStruct" ]

内置的支持类型

这些类型可以默认序列化。

基元(int、string等等), Enum, Nullable<>, TimeSpan, DateTime, DateTimeOffset, Nil, Guid, Uri, Version, StringBuilder, BitArray, ArraySegment<>, BigInteger, Complext, Task, Array[], Array[,], Array[,,], Array[,,,], KeyValuePair<,>, Tuple<,...>, ValueTuple<,...>, List<>, LinkedList<>, Queue<>, Stack<>, HashSet<>, ReadOnlyCollection<>, IList<>, ICollection<>, IEnumerable<>, Dictionary<,>, IDictionary<,>, SortedDictionary<,>, SortedList<,>, ILookup<,>, IGrouping<,>, ObservableCollection<>, ReadOnlyOnservableCollection<>, IReadOnlyList<>, IReadOnlyCollection<>, ISet<>, ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, ReadOnlyDictionary<,>, IReadOnlyDictionary<,>, ConcurrentDictionary<,>, Lazy<>, Task<>, 自定义继承ICollection <>或IDictionary <,>具有无参构造方法, IList,IDictionary和自定义继承ICollection或IDictionary具有无参构造函数(包括ArrayList和Hashtable)。

您可以添加自定义类型的支持和一些官方/第三方扩展包。 对于ImmutableCollections(ImmutableList <>等),对于ReactiveProperty和Unity(Vector3, Quaternion等等),对于F#(Record,FsList,Discriminated Unions等)。

MessagePack.Nil是MessagePack for C#的内置null/void/unit表示类型。

对象序列化

MessagePack for C#可以序列化public Class或Struct,序列化目标必须标记[MessagePackObject]和[Key], Key类型可以选择int或字符串。如果Key类型是int,则使用序列化格式为数组,如果Key类型是字符串,则使用序列化格式为键值对,如果您定义了[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.ToJson(new Sample1 { Foo = 10, Bar = 20 }));

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

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

所有模式序列化目标都是公共实例成员(字段或属性)。 如果要避免序列化目标,可以将[IgnoreMember]添加到目标成员。

目标类必须是 public, 不允许 private, internal 类.

应该使用哪种Key类型,int或string? 作者建议使用int key,因为比string key更快,更紧凑。 但是string key有关键的名字信息,对调试很有用。

MessagePackSerializer序列化目标时,必须在目标使用特性才能保证稳健性,如果类进行了扩充,你必须意识到版本控制。如果Key不存在,MessagePackSerializer将会使用默认值。如果使用的是int key,那么必须从0开始,如果不必要的属性出现,请填写空缺的数字。重用是不好的。 此外,如果Int Key的跳转数字差距太大,则会影响二进制大小。

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

// int key不从0开始并且数字进行了跳跃,将会出现下面的结果
//[null,null,null,0,null,null,null,null,null,null,0]
Console.WriteLine(MessagePackSerializer.ToJson(new IntKeySample()));

如果你想像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.Instance);

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

// 全局设置无约束解析器为默认解析器
MessagePackSerializer.SetDefaultResolver(MessagePack.Resolvers.ContractlessStandardResolver.Instance);

// 序列化
var bin2 = MessagePackSerializer.Serialize(data);

我想序列化私人成员! 默认情况下,不能序列化/反序列化私有成员。 但是你可以使用allow-private解析器来序列化私人成员。

[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.Instance);

我不需要类型,我想像BinaryFormatter那样使用! 你可以使用无类型的解析器和帮助器。 请参阅Typeless部分。

解析器是MessagePack For C#的关键定制点。 详情请见扩展部分。

DataContract兼容性

您可以使用[DataContract]而不是[MessagePackObject]。 如果type标记为DataContract,则可以使用[DataMember]代替[Key],[IgnoreDataMember]代替[IgnoreMember]。

[DataMember(Order = int)] 和 [Key(int)]相同, [DataMember(Name = string)]和 [Key(string)]相同. 如果使用 [DataMember], 则类似于 [Key(nameof(propertyname)].

使用DataContract使其成为一个共享的类库,您不必引用MessagePack for C#。 但是,它不包含在分析器或由mpc.exe生成的代码中。此外,像UnionAttribute,MessagePackFormatterAttribute,SerializationConstructorAttribute等功能不能使用。 出于这个原因,我建议您基本上使用MessagePack for C#特性。

序列化不可变对象(序列化构造器)

MessagePack for C#支持反序列化不可变对象。 例如,这个struct可以自然地序列化/反序列化。

[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);

// Okay to deserialize immutable obejct
var point = MessagePackSerializer.Deserialize<Point>(bin);

MessagePackSerializer choose constructor with the least matched argument, match index if key in integer or match name(ignore case) if key is string. If encounts MessagePackDynamicObjectResolverException: can't find matched constructor parameter you should check about this.

MessagePackSerializer选择具有最少参数的构造方法,如果key是整型将匹配索引或者如果key是字符串将匹配名称(忽略大小写)。 如果遇到 MessagePackDynamicObjectResolverException: can't find matched constructor parameter你应该检查一会下。

如果不能自动匹配,可以通过[SerializationConstructorAttribute]手动指定使用构造函数。

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

    // 如果没有标记特性,将会使用这方法(最少参数)
    public Point(int x)
    {
        X = x;
    }

    [SerializationConstructor]
    public Point(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}
序列化回调

如果对象实现了IMessagePackSerializationCallbackReceiver,则接受OnBeforeSerializeOnAfterDeserialize序列化处理。

[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 for C#支持序列化接口。这就像XmlInclude或ProtoInclude。在MessagePack for C#里叫Union。UnionAttribute只能附加到接口或抽象类。 它需要区分的整型key和子类型

// mark inheritance types
[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 };

// serialize interface.
var bin = MessagePackSerializer.Serialize(data);

// deserialize interface.
var reData = MessagePackSerializer.Deserialize<IUnionSample>(bin);

// use type-switch of C# 7.0
switch (reData)
{
    case FooClass x:
        Console.WriteLine(x.XYZ);
        break;
    case BarClass x:
        Console.WriteLine(x.OPQ);
        break;
    default:
        break;
}

C#7.0 type-switch是Union的最佳选择。 Union被序列化为两个长度的数组。

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.ToJson(bin));

在抽象类中使用Union,你可以像接口那样使用。

[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; }
}

继承类型的序列化,在数组(或键值对)中是扁平化的,对于整型键是无关紧要的,它不能复制父类和所有的子类。

Dynamic(Untyped)反序列化

如果使用MessagePackSerializer.Deserialize<object> 或者MessagePackSerializer.Deserialize<dynamic>,messagepack将转换为 primitive values,msgpack-primitive将转换为bool, char, sbyte, byte, short, int, long, ushort, uint, ulong, float, double, DateTime, string, byte[], object[], IDictionary<object, object>.

// sample binary.
var model = new DynamicModel { Name = "foobar", Items = new[] { 1, 10, 100, 1000 } };
var bin = MessagePackSerializer.Serialize(model, ContractlessStandardResolver.Instance);

// dynamic, untyped
var dynamicModel = MessagePackSerializer.Deserialize<dynamic>(bin, ContractlessStandardResolver.Instance);

Console.WriteLine(dynamicModel["Name"]); // foobar
Console.WriteLine(dynamicModel["Items"][2]); // 100

所以你可以使用索引访问键值对或者数组。

Object 类型序列化

StandardResolverContractlessStandardResolver可以通过DynamicObjectTypeFallbackResolver将Object类型序列化为具体类型。

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

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

// Support Anonymous Type Serialize
var anonType = new { Foo = 100, Bar = "foobar" };
var bin2 = MessagePackSerializer.Serialize(anonType, MessagePack.Resolvers.ContractlessStandardResolver.Instance);

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

Unity支持是有限的。

反序列化时,与Dynamic(Untyped)反序列化相同。

Typeless

Typeless API就像BinaryFormatter, 将类型信息嵌入到二进制中,所以不需要类型去反序列化.

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

// serialize to typeless
var bin = MessagePackSerializer.Typeless.Serialize(mc);

// binary data is embeded type-assembly information.
// ["Sandbox.MyClass, Sandbox",10,"hoge","huga"]
Console.WriteLine(MessagePackSerializer.ToJson(bin));

// can deserialize to MyClass with typeless
var objModel = MessagePackSerializer.Typeless.Deserialize(bin) as MyClass;

类型信息由mspgack ext格式序列化,typecode为100。

MessagePackSerializer.TypelessSerialize / Deserialize <object>(TypelessContractlessStandardResolver.Instance)的快捷方式。 如果要配置默认的Typeless解析器,可以通过MessagePackSerializer.Typeless.RegisterDefaultResolver进行设置。

性能

与其他序列化器在Windows 10 Pro x64 Intel Core i7-6700K 4.00GHz, 32GB RAM上进行Benchmarks比较,Benchmark代码在这-版本信息,ZeroFormatter和FlatBuffers具有非常快速的反序列化器,因此忽略反序列化的性能。

序列化 反序列化 MessagePack for C#_干货_04

MessagePack for C#使用许多技术来提高性能。

  • 序列化只使用ref byte []和int offset,不使用(Memory)Stream(调用Stream api会有开销)
  • 高级API使用内部内存池,分配工作内存不要低于64k
  • 不创建中间实用程序实例(XxxWriter / Reader,XxxContext等)
  • 所有代码避免装箱,所有平台(包括Unity / IL2CPP)
  • 对静态泛型字段生成的格式化程序进行缓存,查找时从缓存查找(不使用字典缓存,因为字典查找需要一定开销)
  • 重新调整的动态代码生成
  • 当代码生成知道目标是primitive时直接调用PrimitiveAPI
  • 当代码生成知道目标(整数/字符串)范围时,减少可变长度格式的分支
  • 不在迭代集合上使用IEnumerable<T> 抽象
  • 使用预先生成的查找表来减少检查消息包类型所耗时间
  • 对非泛型方法使用优化类型key字典
  • 避免查找映射(字符串键)键的字符串键解码,并使用自动化名称查找与il内联代码生成
  • 对于字符串键编码,预先生成的成员名字节并在IL中使用固定大小的二进制副本

在创建这个库之前,作则实现了一个具有ZeroFormatter#Performance的快速序列化器。 这是一个进一步演变的实现。 MessagePack for C#始终是快速的,为所有类型(原始,小结构,大对象,任何集合)进行了优化。

反序列化中每个方法的性能

性能取决于选项。 这是一个BenchmarkDotNet的微型benchamark。 目标对象有9个成员(MyProperty1〜MyProperty9),值为零。

Method Mean Error Scaled Gen 0 Allocated
IntKey 72.67 ns NA 1.00 0.0132 56 B
StringKey 217.95 ns NA 3.00 0.0131 56 B
Typeless_IntKey 176.71 ns NA 2.43 0.0131 56 B
Typeless_StringKey 378.64 ns NA 5.21 0.0129 56 B
MsgPackCliMap 1,355.26 ns NA 18.65 0.1431 608 B
MsgPackCliArray 455.28 ns NA 6.26 0.0415 176 B
ProtobufNet 265.85 ns NA 3.66 0.0319 136 B
Hyperion 366.47 ns NA 5.04 0.0949 400 B
JsonNetString 2,783.39 ns NA 38.30 0.6790 2864 B
JsonNetStreamReader 3,297.90 ns NA 45.38 1.4267 6000 B
JilString 553.65 ns NA 7.62 0.0362 152 B
JilStreamReader 1,408.46 ns NA 19.38 0.8450 3552 B

IntKey,StringKey,Typeless_IntKey,Typeless_StringKey都是MessagePack for C#的方法
,在反序列化过程中实现零内存分配。JsonNetString /JilString从字符串反序列化。JsonStStreamReader / JilStreamReader是从StreamReader的UTF8 byte []中反序列化的。反序列化通常从Stream读取。 因此,它将从字节数组(或流)而不是字符串中读取。

MessagePack for C#IntKey是最快的。 StringKey比IntKey慢,因为StringKey需要从字符串进行匹配。 如果是IntKey,读取数组长度,根据数组长度进行for循环二进制解码。 如果StringKey,读取map 长度,根据map长度循环,首先需要对密钥解码,然后按照key查找,最后二进制解码,则需要额外两个步骤(解码密钥和按键查找)。

字符串键通常是有用的,无约束的,简单的JSON替换,与其他语言的互操作性,以及更多的某些版本。 MessagePack for C#也为String Key进行了优化。 首先,它不会将UTF8字节数组解码为与成员名称匹配的字符串,它会按原样查找字节数组(避免解码成本和额外分配)。

它会尝试匹配每个长整型(long)(每8个字符,如果长度不够,填充0)使用automata和在生成时内联IL代码。

序列化 反序列化 MessagePack for C#_干货_05

这也避免了计算字节数组的哈希码,并且可以在长单元上进行多次比较。

这是ILSpy生成的反序列化器代码的示例的反编译。

序列化 反序列化 MessagePack for C#_干货_06

https://github.com/neuecc/MessagePack-CSharp#performance

如果节点数量很大,则使用嵌入式二进制搜索进行搜索。

另外请注意,这是序列化的基准测试结果。

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也不错。

LZ4压缩

MessagePack是一个快速和紧凑的格式,但它不是压缩格式。 LZ4是非常快速的压缩算法,使用MessagePack for C#可以实现极快的性能和非常紧凑的二进制大小!

MessagePack for C#具有内置的LZ4支持。 您可以使用LZ4MessagePackSerializer而不是MessagePackSerializer。 内建支持是特殊的,作者已经创建了序列化压缩管道,并专门调整了管道,所以共享工作内存,不分配,不要调整,直到完成。

序列化二进制不是简单地压缩lz4二进制。 序列化二进制是有效的MessagePack二进制使用ext格式和自定义typecode(99)。

var array= Enumerable.Range(1, 100).Select(x => new MyClass { Age = 5, FirstName = "foo", LastName = "bar" }).ToArray();

// call LZ4MessagePackSerializer instead of MessagePackSerializer, api is completely same
var lz4Bytes = LZ4MessagePackSerializer.Serialize(array);
var mc2 = LZ4MessagePackSerializer.Deserialize<MyClass[]>(lz4Bytes);

// you can dump lz4 message pack
// [[5,"hoge","huga"],[5,"hoge","huga"],....]
var json = LZ4MessagePackSerializer.ToJson(lz4Bytes);
Console.WriteLine(json);

// lz4Bytes is valid MessagePack, it is using ext-format( [TypeCode:99, SourceLength|CompressedBinary] )
// [99,"0gAAA+vf3ABkkwWjZm9vo2JhcgoA////yVBvo2Jhcg=="]
var rawJson = MessagePackSerializer.ToJson(lz4Bytes);
Console.WriteLine(rawJson);
与protobuf,JSON,ZeroFormatter比较

protbuf-net是.NET上最常用的二进制格式化库。 我(作者)喜欢protobuf-net,并尊重那伟大的工作。 但是如果使用protobuf-net作为通用序列化格式,则可能会引起烦人的问题。

[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 = new int[0] });

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

    Console.WriteLine(result.Array == null); // True, null!
}

protobuf(-net)不能正确处理null和空集合。 因为protobuf没有null表示(这是protobuf-net作者的答案)。

MessagePack规范可以完全序列化C#类型。 这就是推荐MessagePack而不是protobuf的原因。

Protocol Buffers具有良好的IDL和gRPC,这比MessagePack好得多。 如果你想使用IDL,我(作者)推荐Google.Protobuf。

JSON是很好的通用格式。 这是完美的,简单的,足够规范的。 Utf8Json创建了我采用与MessagePack for C#相同的体系结构,并避免编码/修饰成本,所以像二进制一样工作。 如果你想了解二进制与文本,请参阅Utf8Json /应使用哪个序列化器部分。

ZeroFormatter与FlatBuffers类似,但专门用于C#。 这是特别的。 反序列化速度非常快,但是二进制大小却很大。 而ZeroFormatter的缓存算法需要额外的内存。

ZeroFormatter也是特别的。 当与ZeroFormatter对比的情况下,它显示格式化的力量。 但是对于许多常见的用途,MessagePack for C#会更好。

扩展

MessagePack for C#具有扩展点,您可以添加外部类型的序列化支持。 下列是官方扩展支持。

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

MessagePack.ImmutableCollection添加对 System.Collections.Immutable的支持. 添加了对ImmutableArray<>, ImmutableList<>, ImmutableDictionary<,>, ImmutableHashSet<>, ImmutableSortedDictionary<,>, ImmutableSortedSet<>, ImmutableQueue<>, ImmutableStack<>, IImmutableList<>, IImmutableDictionary<,>, IImmutableQueue<>, IImmutableSet<>, IImmutableStack<>的序列化支持.

MessagePack.ReactiveProperty包添加对ReactiveProperty库的支持。它增加了ReactiveProperty <>,IReactiveProperty <>,IReadOnlyReactiveProperty <>,ReactiveCollection <>,unit序列化支持。 这对保存视图模型状态很有用。

MessagePack.AspNetCoreMvcFormatter是ASP.NET Core MVC序列化的附加组件,可提升性能。 这是配置示例。

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

更多信息请访问github: https://github.com/neuecc/MessagePack-CSharp

简介

MessagePack for C#(MessagePack-CSharp)是用于C#的极速MessagePack序列化程序,比MsgPack-Cli快10倍,与其他所有C#序列化程序相比,具有最好的性能。 MessagePack for C#具有内置的LZ4压缩功能,可以实现超快速序列化和二进制占用空间小。 性能永远是重要的! 可用于游戏,分布式计算,微服务,数据存储到Redis等。支持.NET, .NET Core, Unity, Xamarin。

序列化 反序列化 MessagePack for C#_干货

从上图我们看出MessagePack for C#在性能测试中是最好的,这里解释一下第三个MsgPack-Cli是MessagePack官方实现的。第一和第二都是MessagePack for C#,第一项相比第二项具有稍快一点的序列化和反序列化速度,但是第二项采用了L4压缩功能,显著的减少了二进制的大小。在实际使用中推荐使用L4压缩功能。

使用

该组件已经发布在Nuget,使用命令加入项目。

Install-Package MessagePack

分析器

Install-Package MessagePackAnalyzer

扩展

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

Unity在此处下载 https://github.com/neuecc/MessagePack-CSharp/releases

快速开始

定义一个类添加[MessagePackObject]特性,公共成员(属性或者字段)添加[Key]特性,调用MessagePackSerializer.Serialize<T>/Deserialize<T>进行序列化和反序列化,ToJson可以帮我们转储二进制为json格式。

// 标记 MessagePackObjectAttribute
[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; }

    // 公共成员中不序列化目标,标记IgnoreMemberAttribute
    [IgnoreMember]
    public string FullName { get { return FirstName + LastName; } }
}
class Program
{
    static void Main(string[] args)
    {
        var mc = new MyClass
        {
            Age = 99,
            FirstName = "hoge",
            LastName = "huga",
        };

        // 序列化
        var bytes = MessagePackSerializer.Serialize(mc);
        //反序列化
        var mc2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

        // 你可以将msgpack二进制转储为可读的json。
        // 在默认情况下,MeesagePack for C#减少了属性名称信息。
        // [99,"hoge","huga"]
        var json = MessagePackSerializer.ToJson(bytes);
        Console.WriteLine(json);

        Console.ReadKey();

    }
}

序列化索引将会影响该信息在序列化数据中的位置

默认情况下特性是必须的,但是我们有方法进行改变,让它变为不是必须的,详情请看后面。

分析器

MessagePackAnalyzer 可以帮助我们定义对象. 如果不符合规则,那么特性, 程序集等可以被检测到,如果我们编译就会出现编译错误。

序列化 反序列化 MessagePack for C#_干货_02

如果要允许特定类型(例如,注册自定义类型时),请将MessagePackAnalyzer.json放在项目根目录下,并将生成操作设置为AdditionalFiles(其他文件)。

序列化 反序列化 MessagePack for C#_干货_03

这是MessagePackAnalyzer.json内容的一个示例。

[ "MyNamespace.FooClass", "MyNameSpace.BarStruct" ]

内置的支持类型

这些类型可以默认序列化。

基元(int、string等等), Enum, Nullable<>, TimeSpan, DateTime, DateTimeOffset, Nil, Guid, Uri, Version, StringBuilder, BitArray, ArraySegment<>, BigInteger, Complext, Task, Array[], Array[,], Array[,,], Array[,,,], KeyValuePair<,>, Tuple<,...>, ValueTuple<,...>, List<>, LinkedList<>, Queue<>, Stack<>, HashSet<>, ReadOnlyCollection<>, IList<>, ICollection<>, IEnumerable<>, Dictionary<,>, IDictionary<,>, SortedDictionary<,>, SortedList<,>, ILookup<,>, IGrouping<,>, ObservableCollection<>, ReadOnlyOnservableCollection<>, IReadOnlyList<>, IReadOnlyCollection<>, ISet<>, ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, ReadOnlyDictionary<,>, IReadOnlyDictionary<,>, ConcurrentDictionary<,>, Lazy<>, Task<>, 自定义继承ICollection <>或IDictionary <,>具有无参构造方法, IList,IDictionary和自定义继承ICollection或IDictionary具有无参构造函数(包括ArrayList和Hashtable)。

您可以添加自定义类型的支持和一些官方/第三方扩展包。 对于ImmutableCollections(ImmutableList <>等),对于ReactiveProperty和Unity(Vector3, Quaternion等等),对于F#(Record,FsList,Discriminated Unions等)。

MessagePack.Nil是MessagePack for C#的内置null/void/unit表示类型。

对象序列化

MessagePack for C#可以序列化public Class或Struct,序列化目标必须标记[MessagePackObject]和[Key], Key类型可以选择int或字符串。如果Key类型是int,则使用序列化格式为数组,如果Key类型是字符串,则使用序列化格式为键值对,如果您定义了[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.ToJson(new Sample1 { Foo = 10, Bar = 20 }));

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

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

所有模式序列化目标都是公共实例成员(字段或属性)。 如果要避免序列化目标,可以将[IgnoreMember]添加到目标成员。

目标类必须是 public, 不允许 private, internal 类.

应该使用哪种Key类型,int或string? 作者建议使用int key,因为比string key更快,更紧凑。 但是string key有关键的名字信息,对调试很有用。

MessagePackSerializer序列化目标时,必须在目标使用特性才能保证稳健性,如果类进行了扩充,你必须意识到版本控制。如果Key不存在,MessagePackSerializer将会使用默认值。如果使用的是int key,那么必须从0开始,如果不必要的属性出现,请填写空缺的数字。重用是不好的。 此外,如果Int Key的跳转数字差距太大,则会影响二进制大小。

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

// int key不从0开始并且数字进行了跳跃,将会出现下面的结果
//[null,null,null,0,null,null,null,null,null,null,0]
Console.WriteLine(MessagePackSerializer.ToJson(new IntKeySample()));

如果你想像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.Instance);

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

// 全局设置无约束解析器为默认解析器
MessagePackSerializer.SetDefaultResolver(MessagePack.Resolvers.ContractlessStandardResolver.Instance);

// 序列化
var bin2 = MessagePackSerializer.Serialize(data);

我想序列化私人成员! 默认情况下,不能序列化/反序列化私有成员。 但是你可以使用allow-private解析器来序列化私人成员。

[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.Instance);

我不需要类型,我想像BinaryFormatter那样使用! 你可以使用无类型的解析器和帮助器。 请参阅Typeless部分。

解析器是MessagePack For C#的关键定制点。 详情请见扩展部分。

DataContract兼容性

您可以使用[DataContract]而不是[MessagePackObject]。 如果type标记为DataContract,则可以使用[DataMember]代替[Key],[IgnoreDataMember]代替[IgnoreMember]。

[DataMember(Order = int)] 和 [Key(int)]相同, [DataMember(Name = string)]和 [Key(string)]相同. 如果使用 [DataMember], 则类似于 [Key(nameof(propertyname)].

使用DataContract使其成为一个共享的类库,您不必引用MessagePack for C#。 但是,它不包含在分析器或由mpc.exe生成的代码中。此外,像UnionAttribute,MessagePackFormatterAttribute,SerializationConstructorAttribute等功能不能使用。 出于这个原因,我建议您基本上使用MessagePack for C#特性。

序列化不可变对象(序列化构造器)

MessagePack for C#支持反序列化不可变对象。 例如,这个struct可以自然地序列化/反序列化。

[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);

// Okay to deserialize immutable obejct
var point = MessagePackSerializer.Deserialize<Point>(bin);

MessagePackSerializer choose constructor with the least matched argument, match index if key in integer or match name(ignore case) if key is string. If encounts MessagePackDynamicObjectResolverException: can't find matched constructor parameter you should check about this.

MessagePackSerializer选择具有最少参数的构造方法,如果key是整型将匹配索引或者如果key是字符串将匹配名称(忽略大小写)。 如果遇到 MessagePackDynamicObjectResolverException: can't find matched constructor parameter你应该检查一会下。

如果不能自动匹配,可以通过[SerializationConstructorAttribute]手动指定使用构造函数。

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

    // 如果没有标记特性,将会使用这方法(最少参数)
    public Point(int x)
    {
        X = x;
    }

    [SerializationConstructor]
    public Point(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}
序列化回调

如果对象实现了IMessagePackSerializationCallbackReceiver,则接受OnBeforeSerializeOnAfterDeserialize序列化处理。

[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 for C#支持序列化接口。这就像XmlInclude或ProtoInclude。在MessagePack for C#里叫Union。UnionAttribute只能附加到接口或抽象类。 它需要区分的整型key和子类型

// mark inheritance types
[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 };

// serialize interface.
var bin = MessagePackSerializer.Serialize(data);

// deserialize interface.
var reData = MessagePackSerializer.Deserialize<IUnionSample>(bin);

// use type-switch of C# 7.0
switch (reData)
{
    case FooClass x:
        Console.WriteLine(x.XYZ);
        break;
    case BarClass x:
        Console.WriteLine(x.OPQ);
        break;
    default:
        break;
}

C#7.0 type-switch是Union的最佳选择。 Union被序列化为两个长度的数组。

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.ToJson(bin));

在抽象类中使用Union,你可以像接口那样使用。

[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; }
}

继承类型的序列化,在数组(或键值对)中是扁平化的,对于整型键是无关紧要的,它不能复制父类和所有的子类。

Dynamic(Untyped)反序列化

如果使用MessagePackSerializer.Deserialize<object> 或者MessagePackSerializer.Deserialize<dynamic>,messagepack将转换为 primitive values,msgpack-primitive将转换为bool, char, sbyte, byte, short, int, long, ushort, uint, ulong, float, double, DateTime, string, byte[], object[], IDictionary<object, object>.

// sample binary.
var model = new DynamicModel { Name = "foobar", Items = new[] { 1, 10, 100, 1000 } };
var bin = MessagePackSerializer.Serialize(model, ContractlessStandardResolver.Instance);

// dynamic, untyped
var dynamicModel = MessagePackSerializer.Deserialize<dynamic>(bin, ContractlessStandardResolver.Instance);

Console.WriteLine(dynamicModel["Name"]); // foobar
Console.WriteLine(dynamicModel["Items"][2]); // 100

所以你可以使用索引访问键值对或者数组。

Object 类型序列化

StandardResolverContractlessStandardResolver可以通过DynamicObjectTypeFallbackResolver将Object类型序列化为具体类型。

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

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

// Support Anonymous Type Serialize
var anonType = new { Foo = 100, Bar = "foobar" };
var bin2 = MessagePackSerializer.Serialize(anonType, MessagePack.Resolvers.ContractlessStandardResolver.Instance);

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

Unity支持是有限的。

反序列化时,与Dynamic(Untyped)反序列化相同。

Typeless

Typeless API就像BinaryFormatter, 将类型信息嵌入到二进制中,所以不需要类型去反序列化.

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

// serialize to typeless
var bin = MessagePackSerializer.Typeless.Serialize(mc);

// binary data is embeded type-assembly information.
// ["Sandbox.MyClass, Sandbox",10,"hoge","huga"]
Console.WriteLine(MessagePackSerializer.ToJson(bin));

// can deserialize to MyClass with typeless
var objModel = MessagePackSerializer.Typeless.Deserialize(bin) as MyClass;

类型信息由mspgack ext格式序列化,typecode为100。

MessagePackSerializer.TypelessSerialize / Deserialize <object>(TypelessContractlessStandardResolver.Instance)的快捷方式。 如果要配置默认的Typeless解析器,可以通过MessagePackSerializer.Typeless.RegisterDefaultResolver进行设置。

性能

与其他序列化器在Windows 10 Pro x64 Intel Core i7-6700K 4.00GHz, 32GB RAM上进行Benchmarks比较,Benchmark代码在这-版本信息,ZeroFormatter和FlatBuffers具有非常快速的反序列化器,因此忽略反序列化的性能。

序列化 反序列化 MessagePack for C#_干货_04

MessagePack for C#使用许多技术来提高性能。

  • 序列化只使用ref byte []和int offset,不使用(Memory)Stream(调用Stream api会有开销)
  • 高级API使用内部内存池,分配工作内存不要低于64k
  • 不创建中间实用程序实例(XxxWriter / Reader,XxxContext等)
  • 所有代码避免装箱,所有平台(包括Unity / IL2CPP)
  • 对静态泛型字段生成的格式化程序进行缓存,查找时从缓存查找(不使用字典缓存,因为字典查找需要一定开销)
  • 重新调整的动态代码生成
  • 当代码生成知道目标是primitive时直接调用PrimitiveAPI
  • 当代码生成知道目标(整数/字符串)范围时,减少可变长度格式的分支
  • 不在迭代集合上使用IEnumerable<T> 抽象
  • 使用预先生成的查找表来减少检查消息包类型所耗时间
  • 对非泛型方法使用优化类型key字典
  • 避免查找映射(字符串键)键的字符串键解码,并使用自动化名称查找与il内联代码生成
  • 对于字符串键编码,预先生成的成员名字节并在IL中使用固定大小的二进制副本

在创建这个库之前,作则实现了一个具有ZeroFormatter#Performance的快速序列化器。 这是一个进一步演变的实现。 MessagePack for C#始终是快速的,为所有类型(原始,小结构,大对象,任何集合)进行了优化。

反序列化中每个方法的性能

性能取决于选项。 这是一个BenchmarkDotNet的微型benchamark。 目标对象有9个成员(MyProperty1〜MyProperty9),值为零。

Method Mean Error Scaled Gen 0 Allocated
IntKey 72.67 ns NA 1.00 0.0132 56 B
StringKey 217.95 ns NA 3.00 0.0131 56 B
Typeless_IntKey 176.71 ns NA 2.43 0.0131 56 B
Typeless_StringKey 378.64 ns NA 5.21 0.0129 56 B
MsgPackCliMap 1,355.26 ns NA 18.65 0.1431 608 B
MsgPackCliArray 455.28 ns NA 6.26 0.0415 176 B
ProtobufNet 265.85 ns NA 3.66 0.0319 136 B
Hyperion 366.47 ns NA 5.04 0.0949 400 B
JsonNetString 2,783.39 ns NA 38.30 0.6790 2864 B
JsonNetStreamReader 3,297.90 ns NA 45.38 1.4267 6000 B
JilString 553.65 ns NA 7.62 0.0362 152 B
JilStreamReader 1,408.46 ns NA 19.38 0.8450 3552 B

IntKey,StringKey,Typeless_IntKey,Typeless_StringKey都是MessagePack for C#的方法
,在反序列化过程中实现零内存分配。JsonNetString /JilString从字符串反序列化。JsonStStreamReader / JilStreamReader是从StreamReader的UTF8 byte []中反序列化的。反序列化通常从Stream读取。 因此,它将从字节数组(或流)而不是字符串中读取。

MessagePack for C#IntKey是最快的。 StringKey比IntKey慢,因为StringKey需要从字符串进行匹配。 如果是IntKey,读取数组长度,根据数组长度进行for循环二进制解码。 如果StringKey,读取map 长度,根据map长度循环,首先需要对密钥解码,然后按照key查找,最后二进制解码,则需要额外两个步骤(解码密钥和按键查找)。

字符串键通常是有用的,无约束的,简单的JSON替换,与其他语言的互操作性,以及更多的某些版本。 MessagePack for C#也为String Key进行了优化。 首先,它不会将UTF8字节数组解码为与成员名称匹配的字符串,它会按原样查找字节数组(避免解码成本和额外分配)。

它会尝试匹配每个长整型(long)(每8个字符,如果长度不够,填充0)使用automata和在生成时内联IL代码。

序列化 反序列化 MessagePack for C#_干货_05

这也避免了计算字节数组的哈希码,并且可以在长单元上进行多次比较。

这是ILSpy生成的反序列化器代码的示例的反编译。

序列化 反序列化 MessagePack for C#_干货_06

https://github.com/neuecc/MessagePack-CSharp#performance

如果节点数量很大,则使用嵌入式二进制搜索进行搜索。

另外请注意,这是序列化的基准测试结果。

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也不错。

LZ4压缩

MessagePack是一个快速和紧凑的格式,但它不是压缩格式。 LZ4是非常快速的压缩算法,使用MessagePack for C#可以实现极快的性能和非常紧凑的二进制大小!

MessagePack for C#具有内置的LZ4支持。 您可以使用LZ4MessagePackSerializer而不是MessagePackSerializer。 内建支持是特殊的,作者已经创建了序列化压缩管道,并专门调整了管道,所以共享工作内存,不分配,不要调整,直到完成。

序列化二进制不是简单地压缩lz4二进制。 序列化二进制是有效的MessagePack二进制使用ext格式和自定义typecode(99)。

var array= Enumerable.Range(1, 100).Select(x => new MyClass { Age = 5, FirstName = "foo", LastName = "bar" }).ToArray();

// call LZ4MessagePackSerializer instead of MessagePackSerializer, api is completely same
var lz4Bytes = LZ4MessagePackSerializer.Serialize(array);
var mc2 = LZ4MessagePackSerializer.Deserialize<MyClass[]>(lz4Bytes);

// you can dump lz4 message pack
// [[5,"hoge","huga"],[5,"hoge","huga"],....]
var json = LZ4MessagePackSerializer.ToJson(lz4Bytes);
Console.WriteLine(json);

// lz4Bytes is valid MessagePack, it is using ext-format( [TypeCode:99, SourceLength|CompressedBinary] )
// [99,"0gAAA+vf3ABkkwWjZm9vo2JhcgoA////yVBvo2Jhcg=="]
var rawJson = MessagePackSerializer.ToJson(lz4Bytes);
Console.WriteLine(rawJson);
与protobuf,JSON,ZeroFormatter比较

protbuf-net是.NET上最常用的二进制格式化库。 我(作者)喜欢protobuf-net,并尊重那伟大的工作。 但是如果使用protobuf-net作为通用序列化格式,则可能会引起烦人的问题。

[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 = new int[0] });

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

    Console.WriteLine(result.Array == null); // True, null!
}

protobuf(-net)不能正确处理null和空集合。 因为protobuf没有null表示(这是protobuf-net作者的答案)。

MessagePack规范可以完全序列化C#类型。 这就是推荐MessagePack而不是protobuf的原因。

Protocol Buffers具有良好的IDL和gRPC,这比MessagePack好得多。 如果你想使用IDL,我(作者)推荐Google.Protobuf。

JSON是很好的通用格式。 这是完美的,简单的,足够规范的。 Utf8Json创建了我采用与MessagePack for C#相同的体系结构,并避免编码/修饰成本,所以像二进制一样工作。 如果你想了解二进制与文本,请参阅Utf8Json /应使用哪个序列化器部分。

ZeroFormatter与FlatBuffers类似,但专门用于C#。 这是特别的。 反序列化速度非常快,但是二进制大小却很大。 而ZeroFormatter的缓存算法需要额外的内存。

ZeroFormatter也是特别的。 当与ZeroFormatter对比的情况下,它显示格式化的力量。 但是对于许多常见的用途,MessagePack for C#会更好。

扩展

MessagePack for C#具有扩展点,您可以添加外部类型的序列化支持。 下列是官方扩展支持。

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

MessagePack.ImmutableCollection添加对 System.Collections.Immutable的支持. 添加了对ImmutableArray<>, ImmutableList<>, ImmutableDictionary<,>, ImmutableHashSet<>, ImmutableSortedDictionary<,>, ImmutableSortedSet<>, ImmutableQueue<>, ImmutableStack<>, IImmutableList<>, IImmutableDictionary<,>, IImmutableQueue<>, IImmutableSet<>, IImmutableStack<>的序列化支持.

MessagePack.ReactiveProperty包添加对ReactiveProperty库的支持。它增加了ReactiveProperty <>,IReactiveProperty <>,IReadOnlyReactiveProperty <>,ReactiveCollection <>,unit序列化支持。 这对保存视图模型状态很有用。

MessagePack.AspNetCoreMvcFormatter是ASP.NET Core MVC序列化的附加组件,可提升性能。 这是配置示例。

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

更多信息请访问github: https://github.com/neuecc/MessagePack-CSharp