包序列化模式
定义
命名空间:TouchSocket.Core
程序集:TouchSocket.Core.dll
一、说明
包序列化模式是为了解决极限序列化的问题。常规序列化的瓶颈,主要是反射、表达式树、创建对象等几个方面,这几个问题在运行时阶段,都没有一个好的解决方案。目前在net6以后,微软大力支持源代码生成,这使得这类问题得到了很大程度的解决。但是对于老项目,或者无法使用net6和vs2022以上的项目,是无法使用的。所以,这时候包序列化模式就显得非常需要了。
二、特点
【优点】
- 简单、可靠、高效
- 可以支持所有类型(需要自己编写代码)
- 数据量最少(从理论来说这是占数据量最轻量的设计)
【缺点】
- 要求序列化端和反序列化端必须保持一致,可以存在数据差异,但是不能出现数据断层。
三、使用
【实体类】
class MyClass : IPackage
{
public int P1 { get; set; }
public string P2 { get; set; }
public char P3 { get; set; }
public double P4 { get; set; }
public List<int> P5 { get; set; }
public Dictionary<int, MyClassModel> P6 { get; set; }
public void Package(ByteBlock byteBlock)
{
//基础类型直接写入。
byteBlock.Write(P1);
byteBlock.Write(P2);
byteBlock.Write(P3);
byteBlock.Write(P4);
//集合类型,可以先判断是否为null
byteBlock.WriteIsNull(P5);
if (P5 != null)
{
//如果不为null
//就先写入集合长度
//然后遍历将每个项写入
byteBlock.Write(P5.Count);
foreach (var item in P5)
{
byteBlock.Write(item);
}
}
//字典类型,可以先判断是否为null
byteBlock.WriteIsNull(P6);
if (P6 != null)
{
//如果不为null
//就先写入字典长度
//然后遍历将每个项,按键、值写入
byteBlock.Write(P6.Count);
foreach (var item in P6)
{
byteBlock.Write(item.Key);
byteBlock.WritePackage(item.Value);//因为值MyClassModel实现了IPackage,所以可以直接写入
}
}
}
public void Unpackage(ByteBlock byteBlock)
{
//基础类型按序读取。
this.P1 = byteBlock.ReadInt32();
this.P2 = byteBlock.ReadString();
this.P3 = byteBlock.ReadChar();
this.P4 = byteBlock.ReadDouble();
var isNull = byteBlock.ReadIsNull();
if (!isNull)
{
int count = byteBlock.ReadInt32();
var list = new List<int>(count);
for (int i = 0; i < count; i++)
{
list.Add(byteBlock.ReadInt32());
}
this.P5 = list;
}
isNull = byteBlock.ReadIsNull();//复用前面的变量,省的重新声明
if (!isNull)
{
int count = byteBlock.ReadInt32();
var dic = new Dictionary<int, MyClassModel>(count);
for (int i = 0; i < count; i++)
{
dic.Add(byteBlock.ReadInt32(), byteBlock.ReadPackage<MyClassModel>());
}
this.P6 = dic;
}
}
}
class MyClassModel : PackageBase
{
public DateTime P1 { get; set; }
public override void Package(ByteBlock byteBlock)
{
byteBlock.Write(P1);
}
public override void Unpackage(ByteBlock byteBlock)
{
this.P1 = byteBlock.ReadDateTime();
}
}
【打包和解包】
var myClass = new MyClass();
myClass.P1 = 1;
myClass.P2 = "若汝棋茗";
myClass.P3 = 'a';
myClass.P4= 3;
myClass.P5=new List<int> { 1, 2, 3 };
myClass.P6= new Dictionary<int, MyClassModel>()
{
{ 1,new MyClassModel(){ P1=DateTime.Now} },
{ 2,new MyClassModel(){ P1=DateTime.Now} }
};
using (ByteBlock byteBlock=new ByteBlock())
{
myClass.Package(byteBlock);//打包,相当于序列化
byteBlock.Seek(0);//将流位置重置为0
var myNewClass = new MyClass();
myNewClass.Unpackage(byteBlock);//解包,相当于反序列化
}
四、使用源生成
如果源生成可用,使用源代码生成方式,可以实现自动的打包和解包。
例如上述类型,我们只需要使用GeneratorPackage
特性标记即可。
[GeneratorPackage]
internal partial class MyGeneratorPackage : PackageBase
{
public int P1 { get; set; }
public string P2 { get; set; }
public char P3 { get; set; }
public double P4 { get; set; }
public List<int> P5 { get; set; }
public Dictionary<int, MyClassModel> P6 { get; set; }
}
源生成的代码
/*
此代码 由SourceGenerator工具直接生成,非必要请不要修改此处代码
*/
#pragma warning disable
using System;
using System.Diagnostics;
using TouchSocket.Core;
using System.Threading.Tasks;
namespace PackageConsoleApp
{
[global::System.CodeDom.Compiler.GeneratedCode("TouchSocket.SourceGenerator", "2.0.0.0")]
partial class MyGeneratorPackage
{
public override void Package(in ByteBlock byteBlock)
{
byteBlock.Write(P1);
byteBlock.Write(P2);
byteBlock.Write(P3);
byteBlock.Write(P4);
byteBlock.WriteIsNull(this.P5);
if (this.P5 != null)
{
byteBlock.Write(this.P5.Count);
foreach (var item in this.P5)
{
byteBlock.Write(item);
}
}
byteBlock.WriteIsNull(this.P6);
if (this.P6 != null)
{
byteBlock.Write(this.P6.Count);
foreach (var item in this.P6)
{
byteBlock.Write(item.Key);
byteBlock.WritePackage(item.Value);
}
}
}
public override void Unpackage(in ByteBlock byteBlock)
{
P1 = byteBlock.ReadInt32();
P2 = byteBlock.ReadString();
P3 = byteBlock.ReadChar();
P4 = byteBlock.ReadDouble();
if (!byteBlock.ReadIsNull())
{
var len = byteBlock.ReadInt32();
var list = new int[len];
for (var i = 0; i < len; i++)
{
list[i] = byteBlock.ReadInt32();
}
this.P5 = new System.Collections.Generic.List<int>(list);
}
if (!byteBlock.ReadIsNull())
{
var len = byteBlock.ReadInt32();
var dic = new System.Collections.Generic.Dictionary<int, PackageConsoleApp.MyClassModel>(len);
for (var i = 0; i < len; i++)
{
var key = byteBlock.ReadInt32();
PackageConsoleApp.MyClassModel value = null;
if (!byteBlock.ReadIsNull())
{
value = new PackageConsoleApp.MyClassModel();
value.Unpackage(byteBlock);
}
dic.Add(key, value);
}
this.P6 = dic;
}
}
}
}
注意
使用源代码生成方式,需要确保包内的所有成员均为基础类型,或者IPackage
。不然将无法生成。
同时,当包类型是结构体时,才可以直接实现IPackage
接口。如果是实例类,则需要直接或间接使用PackageBase
作为基类。
五、性能评测
基准测试表明:
包序列化模式和使用源代码生成方式工作的MemoryPack几乎一样。比json方式快了10倍多,比微软的json快了近4倍。