Protobuf简单类型直接反序列化方法
我有一个想法,有一个能够进行跨平台的高性能数据协议规范,能够让数据在两个不同的程序之间进行读取,最好能够支持直接将object序列化,那就完美了。
目标
- 支持任意Object序列化
- 支持从类似System.String的字符串中获取类的信息并进行反序列化
- 支持简单对象的直接序列化与反序列化
方案
Xml序列化
说到序列化,.NET自带的XML序列化就很好用了,无奈有很多类型不支持,典型的比如Dictionary<>,而且这个东西虽然强大,但是xml的标签机制导致多余的内容比较多,空间占用会比较大。
Binary序列化
支持任意object序列化,.NET还提供了BinaryFormatter。
// code from https://stackoverflow.com/questions/7442164/c-sharp-and-net-how-to-serialize-a-structure-into-a-byte-array-using-binary
MyObject obj = new MyObject();
byte[] bytes;
IFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
formatter.Serialize(stream, obj);
bytes = stream.ToArray();
}
这种方式支持任意的object进行序列化,不过有一个问题,它和Type模型严格绑定,只支持同一个程序集版本的消息交互,也不支持其他语言编写的程序。
我之前用过这种方式,用于单个程序内的数据快速保存与读取。这种情况下,只是单纯在为了保存object的状态,操作非常便捷,我认为非常合适。
Protobuf
这个东西就是grpc中的数据格式,可以跨平台,支持多种语言,数据是二进制的,压缩率也很高。好吧,就是它了。
如果要在.NET中使用Protobuf协议,经常用的两个类库,一个是Google.Protobuf,另外一个是protobuf.net。详细的区别我就不赘述了,有一篇文章有多个对比。由于我比较喜欢直接使用C#的类型系统,所以我还是听从文章建议,直接使用protobuf.net了。
protobuf-net
对于通信双方都是.NET程序的情况下,使用protobuf不需要直接编写proto文件,可以直接共享数据类的引用。如果是需要与非.NET程序进行通信的话,也可以通过工具生成,直接从proto中读取信息并生成类。回顾一下目标,一条条处理。
- 支持任意对象的序列化
protobuf通过定义实体类来进行序列化,所以也是支持任意对象的。这里我就不再详细说明了,可以在官网查看详细使用方法。
- 支持从类似System.String的字符串中获取类的信息并进行反序列化
一直有一个痛点,能否从序列化后的内容中还原一般对象,就是对象类型在编译的时候未知的那种。通过保存类型的string名称,在需要反序列化的时候,通过类型名称加载类型,将内容反序列化为指定类型。这个多多少少要用到反射了吧。
static void Main(string[] args)
{
var ps = new List<string> { "1346dfg" , "31461sfghj", "24576sth"} ;
var name = ps.GetType().FullName;
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Create))
{
Serializer.Serialize(ms, ps);
}
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Open))
{
//data已经转换为List<string>对象,不过返回的类型还是object,可以强制转换。
dynamic data = Serializer.Deserialize(Type.GetType(name), ms);
Console.WriteLine(data[1]);
}
}
这里使用到了一个Type类型的FullName属性,对于内置类型对象,假设ps的类型是String的话,那个FullName为System.String,返回的内容很简单。但在这个例子中,FullName为System.Collections.Generic.List`1[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]],感觉一下子复杂了很多,而且要命的是,这里明确指明了CoreLib的引用,还有版本声明。如果需要在.NET Core3.1中反序列化,肯定是无法实现。
尝试解决一下,这个System.String不光在.NET 5中有,在其他.NET平台应该都可以支持,所以得想办法去掉System.String的尾巴。
可以试着对FullName下手,但是这个东西有点太长了,而且直接处理字符串我不是很喜欢;试着从Type类型下手。
var ty = Type.GetType(name);
Console.WriteLine(ty.Name);
Console.WriteLine(ty.Namespace);
Console.WriteLine(ty.GenericTypeArguments[0].Name);
Console.WriteLine(ty.GenericTypeArguments[0].Namespace);
//组合相关的代码
dynamic data = Serializer.Deserialize(Type.GetType($"{ty.Namespace}.{ty.Name}" +
$"[{ty.GenericTypeArguments[0].Namespace}.{ty.GenericTypeArguments[0].Name}]"), ms);
Console.WriteLine(data[1]);
稍微修改一下,通过手动连接Namespace与Name属性就可以达到我们的目的了。
List`1这个代表这个泛型里面只有一个参数,我这边就硬编码了,对于其他泛型,可能有多个参数,需要进行鉴别,并调整构造Type名称的代码。
我按照这个思路,完整的代码如下:
static void Main(string[] args)
{
var ps = new List<string> { "1346dfg", "31461sfghj", "24576sth" };
var ty = ps.GetType();
//保存Type名称
var name = $"{ty.Namespace}.{ty.Name}" +
$"[{ty.GenericTypeArguments[0].Namespace}.{ty.GenericTypeArguments[0].Name}]";
//实际的程序不涉及文件操作,这里展示MemoryStream的用法。
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize(ms, ps);
//重置指针,从头开始读
ms.Position = 0;
//使用Type名称反序列化
dynamic data = Serializer.Deserialize(Type.GetType(name), ms);
Console.WriteLine(data[1]);
}
}
- 支持简单对象的直接序列化与反序列化
我说的简单对象,就是系统定义的泛型集合与直接有TypeCode,并且不是object的对象。补充一下,我常用的几种。
内置类型
定义在System命名空间下的类型,包括DateTime,Int32之类的,都是直接System.类型名称的形式。
注意,int和float这种是不行的,需要使用Int32和Single。
泛型集合+内置类型
泛型集合定义在System.Collections.Generic这个命名空间,所以组合起来为System.Collections.Generic.泛型名称`参数数量[System.类型名称]。举两个例子:
List<string>的是System.Collections.Generic.List1[System.String]`。
Dictionary<int,string>的是System.Collections.Generic.Dictionary2[[System.Int32],[System.String]]`。
内置类型数组
直接在名称后添加[]即可,形式为System.类型名称[]。
补充
序列化操作不要求序列化的类型和反序列化的类型完全一致,比如说Array可以与List,IEnumerable进行互换。因此,一些单独定义的、结构比较简单的类型,可以通过内置类型进行反序列化,就没有必要在反序列化的时候加载原始的类了,简化了操作。
[ProtoContract]
public class Message
{
[ProtoMember(1)]
public List<string> values { get; set; }
}
static void Main(string[] args)
{
var ps = new Message { values = new List<string> { "1346dfg", "31461sfghj", "24576sth" } };
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Create))
{
Serializer.Serialize(ms, ps);
}
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Open))
{
//List<string>反序列化,而无需使用Message类。这里Message的FullName是"ConsoleApp6.Program+Message"
dynamic data = Serializer.Deserialize(Type.GetType("System.Collections.Generic.List`1[System.String]"), ms);
Console.WriteLine(data[1]);
}
}
另外,对于上面的dynamic,由于编译的时候不检查,怕操作错误的同学可以进行类型转换。分享一个代码段,可能能有点帮助。
//转换对象为某一种类型
public static T ConvertTo<T>(object value)
{
return (T)Convert.ChangeType(value, typeof(T));
}
如果是限定的几种类型,可以使用switch语句进行判断,并将对象转成T,以进行类型安全的操作。如果不是的话,推荐使用接口来定义类的通用行为,这个回答中提供了一些建议,推荐看看。
参考资料
- 内置类型
- https://stackoverflow.com/questions/7442164/c-sharp-and-net-how-to-serialize-a-structure-into-a-byte-array-using-binary#
Protobuf简单类型直接反序列化方法的更多相关文章
- ProtoBuf序列化和反序列化方法
最近公司需要将以前的协议全都改成ProtoBuf生成的协议,再将结构体打包和解包过程终于到一些问题 ,无法使用Marshal.SizeOf计算结构体大小,最后找了一下ProtoBuf的文档,可以用它自 ...
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...
- webservice返回值为Map类型的处理方法
在写一个webservice的时候,方法的返回值是一个复杂类型,处理方法是写一个结果类(Javabean)作为返回值.想着webservice方法返回值为Map的没写过,然后就试着写了一个简单的Dem ...
- 6.Struts2简单类型数据的接受
简单类型数据的接收 在Action类中定义与请求参数同名的属性, 即,要定义该属性的set方法,便能够使struts2自动接收请求参数并赋予同名属性. 简单类型数据的接受举例: 新建工程项目,名称为: ...
- Newtonsoft.Json 处理多态类型的反序列化
Newtonsoft.Json的序列化和反序列化很成熟也很好用, 最近在处理多态类型的反序列化中遇到了问题, 反序列化后只能到基类,而得不到也不能转换到子类.从网上查询了一番后,需要写一个创建类型的C ...
- [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...
- XAF应用开发教程(二)业务对象模型之简单类型属性
使用过ORM的朋友对这一部分理解起来会非常快,如果没有请自行补习吧:D. 不说废话,首先,我们来开发一个简单的CRM系统,CRM系统第一个信息当然是客户信息.我们只做个简单 的客户信息来了解一下XAF ...
- Simple Factory vs. Factory Method vs. Abstract Factory【简单工厂,工厂方法以及抽象工厂的比较】
I ran into a question on stackoverflow the other day that sort of shocked me. It was a piece of code ...
- 结合实例分析简单工厂模式&工厂方法模式&抽象工厂模式的区别
之前写过一篇关于工厂模式(Factory Pattern)的随笔,里面分析了简单工厂模式,但对于工厂方法和抽象工厂的分析较为简略.这里重新分析分析三者的区别,工厂模式是java设计模式中比较简单的一个 ...
随机推荐
- word教程字体和段落设置
放大/缩小字号:1.选中文字-点击"大A"或"小A" 2.同时摁着ctrl+shift+>/ctrl+shift+<即可 设置标题与正文间距:鼠标放 ...
- Android应用测试指南
一.Android 的 SDK Windows 版本安装 按顺序安装以下内容 1. 安装JDK(Java Development Kit, 即Java开发工具包) 2. 安装Eclipse 集成 ...
- Pytest自动化测试 - 必知必会的一些插件
Pytest拥有丰富的插件架构,超过800个以上的外部插件和活跃的社区,在PyPI项目中以" pytest- *"为标识. 本篇将列举github标星超过两百的一些插件进行实战演示 ...
- C语言讲义——数组和指针
数组名表示的是这个数组的首地址.即如果有int a[10],则a 相当于&a[0]. #include <stdio.h> main() { int a[5]= {1,3,5,7, ...
- AgileConfig-轻量级配置中心 1.1.0 发布,支持应用间配置继承
AgileConfig轻量级配置中心自第一个版本发布不知不觉已经半年了.在并未进行什么推广的情况下收到了250个star,对我有很大的鼓舞,并且也有不少同学试用,并且给出了宝贵的意见,非常感谢他们.其 ...
- CentOS下配置VNC
配置桌面 # 安装gnome桌面环境 yum groupinstall Desktop -y # 安装中文语言支持包(可选) yum groupinstall 'Chinese Support' -y ...
- springmvc跨域问题
1.跨域问题: 按照网上所有的方法试了一遍,都没跨过去,正在无助之际,使用filter按照下面的方法解决的时候出现了转机: 添加filter: package com.thc.bpm.filter; ...
- PyQt(Python+Qt)学习随笔:使用QFontDialog.getFont交互设置应用或部件的字体
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 PyQt中的部件只要是QWidget的派生类都可以在Designer或 ...
- 转2:Python字符编码详解
1. 字符编码简介 1.1. ASCII ASCII(American Standard Code for Information Interchange),是一种单字节的编码.计算机世界里一开始只有 ...
- PyQt(Python+Qt)学习随笔:QListView的layoutMode属性和batchSize属性
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 batchSize属性 该属性是在layoutMode属性设置为Batched时,用于控制每个批量的 ...