简介
MessagePack for C#(MessagePack-CSharp)是用于C#的极速MessagePack序列化程序,比MsgPack-Cli快10倍,与其他所有C#序列化程序相比,具有最好的性能。 MessagePack for C#具有内置的LZ4压缩功能,可以实现超快速序列化和二进制占用空间小。 性能永远是重要的! 可用于游戏,分布式计算,微服务,数据存储到Redis等。支持.NET, .NET Core, Unity, Xamarin。
从上图我们看出MessagePack for C#在性能测试中是最好的,这里解释一下第三个MsgPack-Cli是MessagePack官方实现的。第一和第二都是MessagePack for C#,第一项相比第二项具有稍快一点的序列化和反序列化速度,但是第二项采用了L4压缩功能,显著的减少了二进制的大小。在实际使用中推荐使用L4压缩功能。
使用
该组件已经发布在Nuget,使用命令加入项目。
Install-Package MessagePack
分析器
Install-Package MessagePackAnalyzer
扩展
Install-Package MessagePack.ImmutableCollectionInstall-Package MessagePack.ReactivePropertyInstall-Package MessagePack.UnityShimsInstall-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
可以帮助我们定义对象. 如果不符合规则,那么特性, 程序集等可以被检测到,如果我们编译就会出现编译错误。
如果要允许特定类型(例如,注册自定义类型时),请将MessagePackAnalyzer.json放在项目根目录下,并将生成操作设置为AdditionalFiles
(其他文件)。
这是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 obejctvar 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
,则接受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 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.0switch (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, untypedvar dynamicModel = MessagePackSerializer.Deserialize<dynamic>(bin, ContractlessStandardResolver.Instance);Console.WriteLine(dynamicModel["Name"]); // foobarConsole.WriteLine(dynamicModel["Items"][2]); // 100
所以你可以使用索引访问键值对或者数组。
Object 类型序列化
StandardResolver
和ContractlessStandardResolver
可以通过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 Serializevar 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 typelessvar 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 typelessvar objModel = MessagePackSerializer.Typeless.Deserialize(bin) as MyClass;
类型信息由mspgack ext格式序列化,typecode为100。
MessagePackSerializer.Typeless
是Serialize / 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#使用许多技术来提高性能。
- 序列化只使用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 |
---|