无类型 Typeless
无类型的 API 类似于 BinaryFormatter,因为它会将类型信息嵌入到数据块中,所以在调用 API 时不需要显式指定类型。
MessagePackSerializer.Typeless 是 Serialize/Deserialize<object>(TypelessContractlessStandardResolver.Instance)
的快捷方式。如果你想将其配置为默认解析器,可以使用 MessagePackSerializer.Typeless.RegisterDefaultResolver
。
TypelessFormatter 可以单独使用或与其他解析器组合使用。
/****************************************************文件:Test_07.cs作者:Edision日期:#CreateTime#功能:示例7:typeless
*****************************************************/using MessagePack;
using MessagePack.Formatters;
using UnityEngine;public class Test_07 : MonoBehaviour
{object mc = new MyClass(){Age = 10,FirstName = "hoge",LastName = "huga"};public void Test(){// Serialize with the typeless APIvar bin = MessagePackSerializer.Typeless.Serialize(mc);// 数据块中嵌入了类型-程序集信息。// ["MyClass, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",10,"hoge","huga"]Debug.Log(MessagePackSerializer.ConvertToJson(bin));// 你可以使用无类型的 API 再次反序列化为 MyClass// 请注意,在反序列化调用中不需要显式指定类型// 因为类型信息已嵌入到二进制数据块中var objModel = MessagePackSerializer.Typeless.Deserialize(bin) as MyClass;Debug.Log(objModel);//// Replaced `object` uses the typeless resolvervar resolver = MessagePack.Resolvers.CompositeResolver.Create(new[] { TypelessFormatter.Instance },new[] { MessagePack.Resolvers.StandardResolver.Instance });//如果类型名称后来被更改,你将无法再反序列化旧的数据块。//但是在这种情况下,你可以通过提供自己的 TypelessFormatter.BindToType 函数指定一个回退名称。MessagePack.Formatters.TypelessFormatter.BindToType = typeName =>{if (typeName.StartsWith("SomeNamespace")){typeName = typeName.Replace("SomeNamespace", "AnotherNamespace");}return Type.GetType(typeName, false);};}
}[MessagePackObject]
public class MyClass
{[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; } }
}public class Foo
{// use Typeless(this field only)[MessagePackFormatter(typeof(TypelessFormatter))]public object Bar;
}
从不受信任的来源反序列化数据可能会给你的应用程序引入安全漏洞。根据反序列化过程中使用的设置,不受信任的数据可能能够执行任意代码或导致拒绝服务攻击。不受信任的数据可能来自网络上的不受信任源(例如任何和所有联网客户端),或者在通过未验证连接传输时被中间者篡改,也可能来自可能已被篡改的本地存储,或其他许多来源。MessagePack for C# 不提供任何验证数据或防止其被篡改的方法。请在反序列化之前使用适当的方法验证数据 - 例如 MAC(消息认证码)。
请注意这些攻击场景;过去,许多项目和公司以及序列化库用户都曾因不受信任的用户数据反序列化而遭受损失。
当反序列化不受信任的数据时,通过配置 MessagePackSerializerOptions.Security
属性将 MessagePack 设置为更安全的模式:
var options = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData);// Pass the options explicitly for the greatest control.
T object = MessagePackSerializer.Deserialize<T>(data, options);// Or set the security level as the default.
MessagePackSerializer.DefaultOptions = options;
MessagePack 是一种快速且紧凑的格式,但它不是压缩格式。LZ4 是一种极其快速的压缩算法,通过使用 LZ4,MessagePack for C# 可以实现极快的性能以及极其紧凑的二进制大小!
MessagePack for C# 内置了对 LZ4 的支持。你可以通过修改选项对象并将其传递给 API 来激活它,如下所示:
var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray);
MessagePackSerializer.Serialize(obj, lz4Options);
MessagePack.Unity 在应用程序启动时自动将 UnityResolver 添加到默认选项解析器中,使用 unity 包中的代码启用此序列化功能,如下所示:
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void Init()
{MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.Standard.WithResolver(UnityResolver.InstanceWithStandardResolver);
}
以下是一些在使用 MessagePack for C# 时实现最大性能的建议:
1. 使用索引键
-
使用索引键(
[Key(0)]
)而不是字符串键([Key("name")]
)可以显著提高序列化和反序列化的性能,因为索引键不需要处理和查找键名,且生成的二进制数据更紧凑。 -
示例:
[MessagePackObject] public class Person {[Key(0)]public string Name { get; set; }[Key(1)]public int Age { get; set; } }
2. 启用 LZ4 压缩
-
MessagePack for C# 内置了 LZ4 压缩支持,可以通过配置选项启用。LZ4 是一种快速的压缩算法,可以在不显著降低性能的情况下显著减小数据大小。
-
示例:
var options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray); byte[] binary = MessagePackSerializer.Serialize(obj, options);
3. 优化数据结构
-
尽量使用简单的数据结构,避免嵌套过深的复杂结构,以减少序列化和反序列化的时间。
-
如果需要处理大量数据,可以考虑批量处理,减少单次操作的开销。
4. 使用 IBufferWriter<byte>
或 Stream
API
-
默认情况下,
MessagePackSerializer.Serialize
返回byte[]
,这会涉及额外的内存拷贝。使用IBufferWriter<byte>
或Stream
API 可以直接写入缓冲区,避免不必要的内存分配。 -
示例:
using var stream = new MemoryStream(); MessagePackSerializer.Serialize(stream, obj, options);
5. 避免使用无合同解析器
-
无合同解析器(Contractless)虽然方便,但会牺牲性能。如果性能是首要目标,建议显式使用
[MessagePackObject]
和[Key]
特性。
6. 注意版本控制
-
当类结构发生变化时,使用索引键时应保留废弃成员的索引,直到所有客户端都更新。
7. 调整压缩模式
-
如果数据中存在大量重复内容,启用压缩可以显著减小数据大小。但需要注意,压缩效果因数据而异,建议根据实际数据测试压缩的实际效果。
通过以上优化,可以在使用 MessagePack for C# 时实现更高的性能和更紧凑的数据传输。