内置支持类型:
对象序列化
MessagePack for C# 可以序列化你自己定义的公共类或结构体类型。默认情况下,可序列化的类型必须用 [MessagePackObject]
属性进行注解,成员需要用 [Key]
属性进行注解。键可以是索引(整数)或任意字符串。如果所有键都是索引,则使用数组进行序列化,这在性能和二进制大小方面具有优势。否则,将使用 MessagePack 映射(字典)【MessagePack maps (dictionaries) will be used】。
1、如果你使用 [MessagePackObject(keyAsPropertyName: true)]
,则成员不需要显式的 Key 属性,但会使用字符串键。
2、所有公共实例成员(字段和属性)都将被序列化。如果要忽略某些公共成员,可以使用 [IgnoreMember] 属性对成员进行注解。
3、任何可序列化的结构或类都必须具有公共访问权限;私有和内部结构及类不能被序列化!
4、你应该使用索引键(整数)还是字符串键?我们建议使用索引键以实现更快的序列化和更紧凑的二进制表示,这比字符串键更有优势。然而,在调试时,字符串键中的额外信息可能会非常有用。
/****************************************************
文件:Test_02.cs
作者:Edision
日期:#CreateTime#
功能:示例2
*****************************************************/
using MessagePack;
using UnityEngine;
public class Test_02 : MonoBehaviour
{
public void Test()
{
// [10,20]
Debug.Log($"【Test_02Logo】{MessagePackSerializer.SerializeToJson(new Sample1 { Foo = 10, Bar = 20 })}");
// {"foo":10,"bar":20}
Debug.Log($"【Test_02Logo】{MessagePackSerializer.SerializeToJson(new Sample2 { Foo = 10, Bar = 20 })}");
// {"Foo":10}
Debug.Log($"【Test_02Logo】{MessagePackSerializer.SerializeToJson(new Sample3 { Foo = 10, Bar = 20 })}");
}
[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; }
}
}
5、当类发生变化或扩展时,要注意版本控制。如果序列化的二进制数据中不存在某个键,MessagePackSerializer 会将成员初始化为其默认值,这意味着使用引用类型的成员可能会被初始化为 null。如果你使用索引键(整数),键应从 0 开始并依次递增。如果后期版本停止使用某些成员,你应该保留这些过时的成员(C# 提供了 Obsolete 属性来注解这些成员),直到所有其他客户端有机会更新并移除对这些成员的使用。此外,当索引键的值“跳跃”很大,导致序列中出现空隙时,这将对二进制大小产生负面影响,因为会在结果数组中插入 null 占位符。然而,你不应该重用已删除成员的索引,以避免客户端之间的兼容性问题或在尝试反序列化旧数据块时出现问题。
6、如果你不想显式地用 MessagePackObject/Key 属性进行注解,而是希望像使用 Json.NET 那样使用 MessagePack for C#,你可以利用无合约解析器。
7、如果你想序列化私有成员,可以使用其中一个 *AllowPrivate 解析器。
/****************************************************
文件:Test_03.cs
作者:Edision
日期:#CreateTime#
功能:示例3:索引空隙和结果占位符的例子
*****************************************************/
using MessagePack;
using MessagePack.Resolvers;
using System;
using UnityEngine;
public class Test_03 : MonoBehaviour
{
public void Test()
{
//************************************************************************//
// [null,null,null,0,null,null,null,null,null,null,0]
Debug.Log($"【Test_03Logo】" + MessagePackSerializer.SerializeToJson(new IntKeySample()));
//************************************************************************//
var data = new ContractlessSample { MyProperty1 = 99, MyProperty2 = 9999 };
var bin = MessagePackSerializer.Serialize(data, MessagePack.Resolvers.ContractlessStandardResolver.Options);
// {"MyProperty1":99,"MyProperty2":9999}
Debug.Log($"【Test_03Logo】{MessagePackSerializer.ConvertToJson(bin)}");
//************************************************************************//
// 你也可以将 ContractlessStandardResolver 设置为默认解析器。
// (全局状态;不推荐在编写库代码时使用)
MessagePackSerializer.DefaultOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options;
var bin2 = MessagePackSerializer.Serialize(data);
// {"MyProperty1":99,"MyProperty2":9999}
Debug.Log($"【Test_03Logo】{MessagePackSerializer.ConvertToJson(bin2)}");
//************************************************************************//
var privateData = new PrivateSample();
privateData.SetX(9999);
//你可以选择 StandardResolverAllowPrivate 或 ContractlessStandardResolverAllowPrivate
var privateBin = MessagePackSerializer.Serialize(privateData, ContractlessStandardResolverAllowPrivate.Options);
Debug.Log($"【Test_03Logo】{MessagePackSerializer.ConvertToJson(privateBin)}");//【Test_03Logo】[9999]
// 反序列化
var deserializedData = MessagePackSerializer.Deserialize<PrivateSample>(privateBin, ContractlessStandardResolverAllowPrivate.Options);
deserializedData.PrintPrivateField(); // 输出:Test_03Logo】PrivateField: 9999
}
[MessagePackObject]
public class IntKeySample
{
[Key(3)]
public int A { get; set; }
[Key(10)]
public int B { get; set; }
}
/// <summary>
/// 无合约示例
/// </summary>
public class ContractlessSample
{
public int MyProperty1 { get; set; }
public int MyProperty2 { get; set; }
}
}
[MessagePackObject]
public partial class PrivateSample
{
[Key(0)]
int x;
public void SetX(int v)
{
x = v;
}
public int GetX()
{
return x;
}
public void PrintPrivateField()
{
Debug.Log($"Test_03Logo】PrivateField: {x}");
}
}
序列化回调
实现 IMessagePackSerializationCallbackReceiver 接口的对象将在序列化/反序列化期间收到 OnBeforeSerialize 和 OnAfterDeserialize 调用。
public void Test()
{
var mc = new SampleCallback { Key = 0 };
byte[] bytes = MessagePackSerializer.Serialize(mc);
SampleCallback sc = MessagePackSerializer.Deserialize<SampleCallback>(bytes);
}
[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 在序列化时插入占位符。
-
反序列化时,占位符会被转换为对应类型的默认值。
-
在设计数据结构时,尽量避免不必要的索引间隙,以减少序列化后的数据大小和处理开销。