学校门户网站建设工作汇报,怎么做百度推广网站,用php做电商网站,网站开发培训教程简介
c#中极快的MessagePack序列化器。它比MsgPack-Cli快10倍#xff0c;并且优于其他c#序列化器。
c#的MessagePack还内置了对LZ4压缩的支持——一种非常快的压缩算法。性能非常重要#xff0c;特别是在游戏、分布式计算、微服务或数据缓存等应用程序中。
MessagePack具有…简介
c#中极快的MessagePack序列化器。它比MsgPack-Cli快10倍并且优于其他c#序列化器。
c#的MessagePack还内置了对LZ4压缩的支持——一种非常快的压缩算法。性能非常重要特别是在游戏、分布式计算、微服务或数据缓存等应用程序中。
MessagePack具有紧凑的二进制大小和一套完整的通用表达数据类型。
地址GitHub - MessagePack-CSharp/MessagePack-CSharp: Extremely Fast MessagePack Serializer for C#(.NET, .NET Core, Unity, Xamarin). / msgpack.org[C#]
安装
查看地址中README.md,分别有NuGet packages和UnityPack。对于Unity项目发布页面提供了可下载的.unitypackage文件。当在Unity IL2CPP或Xamarin AOT环境中使用时请仔细阅读预代码生成部分。
configuration中APILevel改为.Net Framework
开始
定义要序列化的结构或类并用[MessagePackObject]属性对其进行注释。用[Key]属性注释值应该序列化的成员(字段和属性)。
using MessagePack;
using UnityEngine;public class Test_MessagePack : MonoBehaviour
{}[MessagePackObject]
public class MyClass
{// Key attributes take a serialization index (or string name)/*键属性接受序列化索引(或字符串名称)*/// The values must be unique and versioning has to be considered as well./*这些值必须是唯一的并且必须考虑版本控制。*/[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.SerializeT/DeserializeT 来序列化/反序列化您的对象实例。您可以使用 ConvertToJson 方法获取任何 MessagePack 二进制数据的可读表示形式。
分析器
MessagePackAnalyzer 包有助于
1. 自动为可序列化对象定义。 2. 在不正确使用属性、成员可访问性等时生成编译器警告。
内置支持的类型以及自定义自行看官方。
对象序列化
MessagePack for C# 可以序列化您自己的公共类或结构类型。默认情况下可序列化类型必须使用 [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; }
}private void Test_02(){// [10,20]Debug.Log(MessagePackSerializer.SerializeToJson(new Sample1 { Foo 10, Bar 20 }));// {foo:10,bar:20}Debug.Log(MessagePackSerializer.SerializeToJson(new Sample2 { Foo 10, Bar 20 }));// {Foo:10}Debug.Log(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; }
}private void Test_03()
{// [null,null,null,0,null,null,null,null,null,null,0]Debug.Log(MessagePackSerializer.SerializeToJson(new IntKeySample()));
}
如果您不希望使用 MessagePackObject/Key 特性进行显式注解而是希望像 e.g. Json.NET 一样使用 MessagePack for C#则可以使用无合同解析器。 private void Test_04(){var data new ContractlessSample { MyProperty1 99, MyProperty2 9999 };var bin MessagePackSerializer.Serialize(data,MessagePack.Resolvers.ContractlessStandardResolver.Options);// {MyProperty1:99,MyProperty2:9999}Debug.Log(MessagePackSerializer.ConvertToJson(bin));// 你也可以将ContractlessStandardResolver设置为默认值。// (全局状态;不建议在编写库代码时使用)MessagePackSerializer.DefaultOptions MessagePack.Resolvers.ContractlessStandardResolver.Options;// Now serializable...var bin2 MessagePackSerializer.Serialize(data);// {MyProperty1:99,MyProperty2:9999}Debug.Log(MessagePackSerializer.ConvertToJson(bin2));}
public class ContractlessSample
{public int MyProperty1 { get; set; }public int MyProperty2 { get; set; }
}
如果您希望序列化私有成员您可以使用 *AllowPrivate 解析器之一。
如果您希望使用 MessagePack for C# 类似于 BinaryFormatter具有无类型的序列化 API则可以使用无类型解析器和助手。请参阅“无类型”部分。 解析器是为 MessagePack for C# 添加自定义类型专用支持的方法。请参考“扩展点”部分。
DataContract compatibility
您可以使用 [DataContract] 标记代替 [MessagePackObject] 标记。如果类型带有 DataContract 标记您可以使用 [DataMember] 标记代替 [Key] 标记以及 [IgnoreDataMember] 替换 [IgnoreMember] 标记。
然后 [DataMember(Order int)] 行为与 [Key(int)] 相同[DataMember(Name string)] 与 [Key(string)] 相同而 [DataMember] 则与 [Key(nameof(成员名))] 相同。
使用 DataContract例如在共享库中可以使您的类/结构独立于 MessagePack for C# 序列化。但是它不被分析器支持也不被 mpc 工具用于代码生成。此外诸如 UnionAttribute、MessagePackFormatter、SerializationConstructor 等功能也无法使用。因此我们建议尽可能使用特定的 MessagePack for C# 注释。
MessagePack for C# 支持只读/不可变 readonly/immutable对象成员的序列化。
MessagePack for C# 支持只读/不可变对象/成员的序列化。例如这个结构体可以被序列化和反序列化。
[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;}
}private void Test_06(){var data new Point(99, 9999);var bin MessagePackSerializer.Serialize(data);//[99,9999]Debug.Log(MessagePackSerializer.ConvertToJson(bin));// 反序列化不可变对象var point MessagePackSerializer.DeserializePoint(bin);//99**9999Debug.Log(${point.X}**{point.Y});}
MessagePackSerializer 将选择与索引键匹配的参数列表最好的构造函数或者使用参数名称作为字符串键。如果找不到合适的构造函数则会抛出 MessagePackDynamicObjectResolverException无法找到匹配的构造函数参数异常。可以使用 [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;}
}
序列化回调
在序列化/反序列化过程中实现了 IMessagePackSerializationCallbackReceiver 接口的对象将收到 OnBeforeSerialize 和 OnAfterDeserialize 调用。
[MessagePackObject]
public class SampleCallback : IMessagePackSerializationCallbackReceiver
{[Key(0)]public int Key { get; set; }public void OnBeforeSerialize(){Debug.Log(OnBefore);}public void OnAfterDeserialize(){Debug.Log(OnAfter);}
}
联合
MessagePack for C# 支持接口类型和抽象类类型的对象的序列化。它的行为类似于 XmlInclude 或 ProtoInclude。在 MessagePack for C# 中这些称为联合。只有接口和抽象类才允许标注联合属性。需要唯一的联合密钥。
// 注释继承类型
[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; }
}private void Test_08(){//IUnionSample data new FooClass() { XYZ 999 };IUnionSample data new BarClass() { OPQ BarClass };// 序列化接口类型的对象。var bin MessagePackSerializer.Serialize(data);// 再次进行反序列化。var reData MessagePackSerializer.DeserializeIUnionSample(bin);// 与c# 7.0中的类型切换一起使用switch (reData){case FooClass x:Debug.Log(x.XYZ);break;case BarClass x:Debug.Log(x.OPQ);break;default:break;}}
请注意在衍生类型中不能重复使用在父类型中已经存在的相同键因为内部会使用单一的扁平数组或映射并且因此不能有重复的索引/键。
动态无类型反序列化
当调用MessagePackSerializer.Deserializeobject 或 MessagePackSerializer.Deserializedynamic时二进制大对象blob中的任何值都会被反序列化为其对应的原始类型。如bool, char, sbyte, byte, short, int, long, ushort, uint, ulong, float, double, DateTime, string, byte[], object[], IDictionaryobject, object.
使用字典索引器语法探索对象树是无类型反序列化的最快选项但它很繁琐且难以阅读和编写。在性能不如代码可读性重要的情况下可以考虑使用ExpandoObject进行反序列化。
对象类型序列化
StandardResolver 和 ContractlessStandardResolver 是 ServiceStack.Text 库中的两种序列化器它们能够处理对象类型或匿名类型的序列化。 private void Test_09(){var objects new object[] { 1, aaa, new FooClass { XYZ 9999 } };var bin MessagePackSerializer.Serialize(objects);// [1,aaa,[9999]]Debug.Log(MessagePackSerializer.ConvertToJson(bin));// 支持匿名类型序列化。var anonType new { Foo 100, Bar foobar };var bin2 MessagePackSerializer.Serialize(anonType, MessagePack.Resolvers.ContractlessStandardResolver.Options);// {Foo:100,Bar:foobar}Debug.Log(MessagePackSerializer.ConvertToJson(bin2));}
在反序列化时 behavior 将与动态非类型反序列化相同。
无类型的
无类型API与BinaryFormatter类似因为它会将类型信息嵌入到blob中因此在调用API时不需要显式指定类型。
安全
从不受信任的源反序列化数据可能会引入应用程序的安全漏洞。根据反序列化期间使用的设置不受信任的数据可能能够执行任意代码或导致拒绝服务攻击。不受信任的数据可能来自网络上的不受信任源例如任何和每个联网客户端或者在通过未认证连接传输时可以被中间人篡改或者来自可能已被篡改的本地存储或其他许多来源。MessagePack for C不提供任何方法来验证数据或使其防篡改。请在反序列化之前使用适当的方法来验证数据 - 例如MAC。
请注意这些攻击情况过去很多项目、公司以及序列化库用户都曾受到过不受信任的用户数据反序列化的困扰。
当反序列化不受信任的数据时请将MessagePack配置为更安全的模式通过配置您的MessagePackSerializerOptions.Security属性
var options MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData);// Pass the options explicitly for the greatest control.
T object MessagePackSerializer.DeserializeT(data, options);// Or set the security level as the default.
MessagePackSerializer.DefaultOptions options;
你还应该避免对不受信任的数据使用无类型的序列化程序/格式/解析器因为这会使不受信任的数据有可能反序列化出未曾预料到的类型从而破坏安全性。
UntrustedData模式只能抵御一些常见的攻击但本身并不是一个完全安全的解决方案。
性能
MessagePack For C使用了许多技术来提高性能。
序列化器使用IBufferWriterbyte而不是System.IO.Stream来减少内存开销。 缓冲区是从池中租借的以减少分配通过减少GC压力保持高吞吐量。 不要创建中间实用工具实例* Writer / * Reader* Context等... 利用动态代码生成和JIT避免值类型的装箱。在禁止JIT的平台上使用AOT生成。 缓存在静态泛型字段上的生成形式不使用字典缓存因为字典查找是开销。参见Resolvers 针对值类型的装箱进行了大量优化的动态IL代码生成和JIT。参见DynamicObjectTypeBuilder。在禁止JIT的平台上使用AOT生成。 当IL代码生成确定目标类型为原始类型时直接调用Primitive API。 减少变量长度格式的分支当IL代码生成知道目标类型整数/字符串范围时 尽可能不要使用IEnumerableT抽象迭代集合参见CollectionFormatterBase及其派生的集合格式器 使用预生成的查找表来减少mgpack类型约束检查参见MessagePackBinary 使用优化的类型键字典用于非泛型方法参见ThreadsafeTypeKeyHashTable 避免查找映射字符串键的字符串键解码使用自动机名称查找并与内联IL代码生成一起使用参见AutomataDictionary 要编码字符串键请使用预生成的成员名字节和IL中的固定大小字节数组副本参见UnsafeMemory.cs 在这个库之前我已经实现了快速序列化器与ZeroFormatter# Performance。这是一个进一步进化的实现。MessagePack For C始终快速且针对所有类型原始类型、小结构、大对象、任何集合进行优化。
字符串内联
msgpack格式不允许在数据流中重用字符串。这自然会导致反序列化器为遇到的每一个字符串创建一个新的字符串对象即使它等于先前遇到的另一个字符串。
当反序列化可能包含重复字符串的数据时让反序列化器花一点额外的时间检查是否以前见过给定的字符串并在已见过的情况下重新使用它是值得的。
要在所有字符串值上启用字符串内联请像这样使用一个在任何标准解析器之前指定StringInterningFormatter的解析器 private void Test_10(){var options MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(new MessagePack.Formatters.IMessagePackFormatter[] { new MessagePack.Formatters.StringInterningFormatter() },new IFormatterResolver[] { StandardResolver.Instance }));var data new ClassOfStrings { InternedString InternedString,OrdinaryString OrdinaryString };var lz4Options MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray);ClassOfStrings newClassOfStrings MessagePackSerializer.DeserializeClassOfStrings(MessagePackSerializer.Serialize(data, lz4Options), options);Debug.Log(${newClassOfStrings.InternedString}**{newClassOfStrings.OrdinaryString});}
[MessagePackObject]
public class ClassOfStrings
{[Key(0)][MessagePackFormatter(typeof(MessagePack.Formatters.StringInterningFormatter))]public string InternedString { get; set; }[Key(1)]public string OrdinaryString { get; set; }
}
如果您正在编写自己的某些含有字符串类型的格式器则也可以直接从您的格式器中调用StringInterningFormatter。
LZ4压缩
MessagePack是一种快速紧凑的格式但它不是压缩。LZ4是一个非常快的压缩算法并且使用它MessagePack for C可以获得极高的性能以及极其紧凑的二进制大小
MessagePack for C内置了LZ4支持。您可以修改options对象并将其传递到这样的API中来激活它
var lz4Options MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray);
MessagePackSerializer.Serialize(obj, lz4Options);
MessagePackCompression有两种模式Lz4Block和Lz4BlockArray。两者都不是简单的二进制LZ4压缩而是集成到序列化管道中的特殊压缩使用MessagePack扩展代码Lz4BlockArray98或Lz4Block99。因此它与其他语言提供的压缩兼容性不佳。
Lz4Block将整个MessagePack序列作为单个LZ4块压缩。这是实现最佳压缩比的简单压缩但在必要时复制整个序列以获取连续内存。
Lz4BlockArray将整个MessagePack序列作为LZ4块数组压缩。压缩/解压块分块不会进入GC的大对象堆但是压缩比稍差。
我们建议使用Lz4BlockArray作为默认压缩方式。为了与MessagePack v1.x兼容请使用Lz4Block。
无论在反序列化时设置了哪种LZ4选项两种方法都可以反序列化。例如当使用Lz4BlockArray选项时可以反序列化使用Lz4Block和Lz4BlockArray的二进制数据。如果未设置压缩选项则无法解压缩并因此不能进行反序列化。
当使用MessagePack for C#时提高性能的最佳实践
MessagePack for C#默认优先考虑最大性能。然而也有些选项牺牲性能以换取方便。
1、使用索引键而不是字符串键Contractless
不同的选项对于反序列化性能部分显示了索引键IntKey和字符串键StringKey性能的结果。索引键将以MessagePack数组的形式序列化对象图。字符串键将以MessagePack地图的形式序列化对象图。例如此类型被序列化为
[MessagePackObject]
public class Person
{[Key(0)] or [Key(name)]public string Name { get; set;}[Key(1)] or [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你可以避免这个开销。
注意如果你正在创建库则推荐使用上述自定义解析器而不是CompositeResolver.Create。此外库不得使用StaticCompositeResolver——因为它是全局状态——以避免兼容性问题。
使用本机解析器
默认情况下MessagePack for C#将以字符串形式序列化GUID。这比.NET原生格式GUID慢得多。Decimal也是如此。如果你的应用程序大量使用GUID或Decimal并且不必担心与其他语言的互操作性则可以分别用NativeGuidResolver和NativeDecimalResolver替换它们。
此外DateTime使用MessagePack时间戳格式进行序列化。通过使用NativeDateTimeResolver可以保留Kind并更快地进行序列化。
注意复制缓冲区
默认情况下MessagePackSerializer.Serialize返回byte[]。最终的byte[]是从内部缓冲池复制的。这是额外的成本。您可以使用IBufferWriterT或Stream API直接写入缓冲区。如果你想在序列化器之外使用缓冲池你应该实现自定义的IBufferWriterbyte或使用Nerdbank.Streams包中的SequenceT等现有缓冲池。
在反序列化期间MessagePackSerializer.Deserialize(ReadOnlyMemory byte buffer)优于Deserialize(Stream stream)重载。这是因为Stream API版本首先读取数据、生成ReadOnlySequence byte然后开始反序列化。
选择压缩
如果有重复数据压缩通常是有效的。在MessagePack中使用字符串键的对象数组Contractless可以有效地进行压缩因为可以对多个重复的属性名称应用压缩。索引键不如字符串键压缩有效但是索引键本身就更小。
IntKeyLz4压缩效果不佳但性能仍然有所降低。另一方面StringKey可以预期对二进制大小产生足够的影响。但是这只是一种示例。根据数据的不同压缩也可能非常有效或者除了减慢你的程序外几乎没有其他影响。还存在可以在值中很好地压缩的情况例如包含许多重复HTML标签的长字符串。重要的是要逐案核实压缩的实际效果。
扩展
MessagePack for C#提供了用于自定义类型的优化序列化支持的扩展点。官方提供了扩展支持包。
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、Quaternion、Color、Bounds、Rect、AnimationCurve、Keyframe、Matrix4x4、Gradient、Color32、RectOffset、LayerMask、Vector2Int、Vector3Int、RangeInt、RectInt、BoundsInt和相应的格式器的支持。它可以启用服务器和Unity客户端之间的正确通信。 private void Test_11(){// Set extensions to default resolver.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;}