C#手写数据类和protoc自动生成类的转换
背景
protobuf作为著名的二进制序列化方案,却不能直接把自己写的数据类序列化,只能使用protoc生成类。对于上百个字段,有嵌套类、列表、字典的玩家数据,如果项目已经大量使用自己写的数据类,手写转换方案是一个灾难。
问题抽象
我们需要在一个手写数据类和一个pb数据类之间建立联系,实现手写>pb、pb>手写的转换方法。
基于泛型接口
如果弄一个泛型接口IToPB<T>,T给pb类,所有要转换的手写类继承这个接口,里面有两个转换方法……我们发现里面可以放到PB类的方法,PB类到手写类的方法需要输入T,返回它的具体子类,这里没法写具体子类,而且没法写成实例方法,它需要new一个实例。那么我们不返回手写类的实例了,我们new一个手写对象,让它执行ToMy(T t),把里面的字段赋值。
public interface IToPB<T>{ public T ToPB(); public void ToMy(T t); }这样并没有减少代码量,只是把转换代码分散到各数据类里。
对于一个大型嵌套数据类,调用ToPB,里面是各字段的ToPB方法,像金字塔一样从简单到复杂垒起来。
基于特性+反射
需要一个类的特性ToPB,参数是它的PB类。一个字段的特性PBField,参数是它的PB类对应字段的名字。反射先看它有没有ToPB,提取类型参数,创建PB实例,再拆解字段,看有没有PBField特性,有则提取字段名,去PB类里找字段,赋值,处理类型不匹配报错。
PB字段名改名后,特性的参数也要改。还没有语法报错,需要我们自己记得。
要加大量特性、特性参数,要写基于反射的转换方法,代码量也不小,比泛型接口还更绕了。
基于自动生成代码
我们可以由手写的C#类自动生成对应的proto脚本,并在C#类里添加和protoc生成类的互相转换方法。
总结
我们不可能全自动的,用很少劳动量实现一个转换方案。
已经写好了C#数据类>
要写互相转换方法,所有数据类都要写>
懒得都写,关卡数据部分直接用proto的,proto数据类比C#多了关卡数据>
C#转proto时直接new,之前的关卡数据丢失>
要么C#数据类一个都不少,要么写一个proto传入的转换方法,破坏接口
扩展:任意大量A型类需要和B型类相互转换
类似的情景还有场景保存,把场景内大量不同的类转换成存档数据类,读档时要把数据类还原成物体,不是简单的new,而是找到预制体,实例化,然后写入字段。
然后就不能先new然后void Load(data)填充字段,需要加载方法把生成物体也包含,那么不能是实例方法。可以是静态方法,但是就不能用接口约束。如果把加载方法写在数据类,那如果是个protobuf生成类,最好不要改写,而是使用扩展方法。
最终方案就2个
- 物体类使用静态方法,放弃用接口约束;
- 给protobuf存档类加扩展方法。如果将来protobuf类改名了,扩展方法也要改一下;
综上,方案1好一点。
