实现目的
在一个游戏项目中可能需要有大量c#类型需要序列化和反序列化(如配置表,游戏运行数据)需要保存或从文本中读取。为了减少一个个去编辑序列化和反序列化的工作量,需要有一个简单的方法对这些c#类型进行处理,一键生成代码。
实现效果
该处声明了一个类,实现了一个接口,并将需要序列化和反序列化的字段用自定义特性标签(SerializeAttribute)封装,再通过反射拿到该类的标签,根据其类型依次写入序列化和反序列化方法。
public class ConfigDataBase : ISerializeable {
// 此处为注册的需要序列化标签
[SerializeAttribute("ID")]
public int ID;
public void AfterDeSerialize()
{
}
public void BeforeSerialize()
{
}
//以下为代码生成部分
#region AutoGenerate
public virtual void Serialize(System.IO.BinaryWriter writer)
{
writer.Write(ID);
}
public virtual void Deserialize(System.IO.BinaryReader reader)
{
ID = reader.ReadInt32();
}
#endregion
}
实现
自定义序列化接口
public interface ISerializeable
{
/// <summary>
/// 在序列化之前调用,只能用于将自己节点引用型转换为数据
/// </summary>
public void BeforeSerialize();
/// <summary>
/// 序列化
/// </summary>
/// <param name="writer">二进制写入器</param>
public void Serialize(BinaryWriter writer);
/// <summary>
/// 在反序列化之后调用,只能写序列化后自己节点处理相关引用型参数
/// </summary>
public void AfterDeSerialize();
/// <summary>
/// 反序列化
/// </summary>
/// <param name="reader">二进制读取器</param>
public void Deserialize(BinaryReader reader);
}
自定义特性标签
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Enum)]
public class SerializeAttribute : Attribute
{
public readonly string name;
public SerializeAttribute(string name) {
this.name = name;
}
}
BinaryWriter,BinaryReader扩展
这里按自己项目需要的类型来,想要支持啥类型就写啥,不需要就不写。
public static class BinaryReaderExpand
{
public static T ReadSerializeable<T>(this BinaryReader reader) where T: ISerializeable,new()
{
T t = new T();
bool isHasValue = reader.ReadBoolean();
if (isHasValue) {
t.Deserialize(reader);
t.AfterDeSerialize();
}
return t;
}
public static int[] ReadIntArr(this BinaryReader reader)
{
int length = reader.ReadInt32();
int[] arr = new int[length];
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadInt32();
}
return arr;
}
public static string[] ReadStringArr(this BinaryReader reader)
{
int length = reader.ReadInt32();
string[] arr = new string[length];
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadString();
}
return arr;
}
public static double[] ReadDoubleArr(this BinaryReader reader)
{
int length = reader.ReadInt32();
double[] arr = new double[length];
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadDouble();
}
return arr;
}
public static bool[] ReadBooleanArr(this BinaryReader reader)
{
int length = reader.ReadInt32();
bool[] arr = new bool[length];
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadBoolean();
}
return arr;
}
public static float[] ReadFloatArr(this BinaryReader reader)
{
int length = reader.ReadInt32();
float[] arr = new float[length];
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadSingle();
}
return arr;
}
public static long[] ReadLongArr(this BinaryReader reader)
{
int length = reader.ReadInt32();
long[] arr = new long[length];
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadInt64();
}
return arr;
}
public static List<int> ReadIntList(this BinaryReader reader)
{
int length = reader.ReadInt32();
List<int> arr = new List<int>(length);
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadInt32();
}
return arr;
}
public static List<T> ReadSerializeableList<T>(this BinaryReader reader) where T: ISerializeable,new()
{
int length = reader.ReadInt32();
List<T> arr = new List<T>(length);
if (length == 0)
{
return arr;
}
for (int i = 0; i < length; i++)
{
arr[i] = reader.ReadSerializeable<T>();
}
return arr;
}
}
public static class BinaryWriterExpand
{
public static void WriteSerializeable(this BinaryWriter writer, ISerializeable sa)
{
writer.Write(sa != null);
if (sa == null) return;
sa.BeforeSerialize();
sa.Serialize(writer);
}
public static void Write(this BinaryWriter writer, int[] arr)
{
writer.Write(arr.Length);
if (arr.Length ==0) {
return;
}
foreach (var item in arr)
{
writer.Write(item);
}
}
public static void Write(this BinaryWriter writer, string[] arr)
{
writer.Write(arr.Length);
if (arr.Length ==0) {
return;
}
foreach (var item in arr)
{
writer.Write(item);
}
}
public static void Write(this BinaryWriter writer, double[] arr)
{
writer.Write(arr.Length);
if (arr.Length ==0) {
return;
}
foreach (var item in arr)
{
writer.Write(item);
}
}
public static void Write(this BinaryWriter writer, bool[] arr)
{
writer.Write(arr.Length);
if (arr.Length ==0) {
return;
}
foreach (var item in arr)
{
writer.Write(item);
}
}
public static void Write(this BinaryWriter writer, float[] arr)
{
writer.Write(arr.Length);
if (arr.Length == 0)
{
return;
}
foreach (var item in arr)
{
writer.Write(item);
}
}
public static void Write(this BinaryWriter writer, long[] arr)
{
writer.Write(arr.Length);
if (arr.Length == 0)
{
return;
}
foreach (var item in arr)
{
writer.Write(item);
}
}
public static void Write(this BinaryWriter writer, List<int> arr)
{
writer.Write(arr.Count);
if (arr.Count == 0)
{
return;
}
foreach (var item in arr)
{
writer.Write(item);
}
}
public static void Write(this BinaryWriter writer, List<ISerializeable> arr)
{
writer.Write(arr.Count);
if (arr.Count == 0)
{
return;
}
foreach (var item in arr)
{
writer.WriteSerializeable(item);
}
}
}
注册需要编译的代码列表
简单来说就是声明编译的类的类型和文件路径。方便编译。
public static class GenerateRegister
{
public static List<GenerateInfo> list = new List<GenerateInfo>();
public static List<GenerateInfo> Glist = new List<GenerateInfo>();
static GenerateRegister()
{
GameConfigRegister();
Register();
}
private static void GameConfigRegister()
{
}
private static void Register()
{
//model
AddAutoGenerate<PlayerModel>("PlayerModel", "Scripts/Model");
AddAutoGenerate<LevelModel>("LevelModel", "Scripts/Model");
AddAutoGenerate<MoneyModel>("MoneyModel", "Scripts/Model");
AddAutoGenerate<GameController>("GameController", "Scripts");
AddAutoGenerate<HeroData>("HeroData", "Scripts/Hero");
AddAutoGenerate<MonitorGroup>("MonitorGroup", "Scripts/MonitorListener");
AddAutoGenerate<HeroTransfeData>("HeroTransfeData", "Scripts/Hero");
AddAutoGenerate<ConfigDataBase>("ConfigBase", "Scripts/config");
AddAutoGenerate<ConfigDataBase>("ConfigBase", "Scripts/config");
}
public static void AddAutoGenerate<T>(string name,string cspath) {
GenerateInfo info = new GenerateInfo(typeof(T), name, cspath);
Glist.Add(info);
}
private static void AddSaveInfo<T>(string name, string path) {
GenerateInfo info = new GenerateInfo(typeof(T),name,path);
list.Add(info);
}
}
public class GenerateInfo
{
public Type t;
public string SavePath;
public string name;
private List<MemberInfo> sFullProps;
public GenerateInfo(Type t,string name, string path){
this.t = t;
this.name = name;
this.SavePath = path;
}
public List<MemberInfo> GetFullProps()
{
// 第一次调用生成一次以后就不需要了
sFullProps = new List<MemberInfo>();
PropertyInfo[] pinfo = t.GetProperties();
FieldInfo[] finfo = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic| BindingFlags.Instance);
foreach (var item in pinfo)
{
if (item.GetCustomAttribute<SerializeAttribute>() != null)
{
sFullProps.Add(item);
}
}
foreach (var item in finfo)
{
if (item.GetCustomAttribute<SerializeAttribute>() != null)
{
sFullProps.Add(item);
}
}
return sFullProps;
}
}
编译(核心代码)
因为这个部分我使用的是unity引擎,所以采用的是unity扩展的方式。
简单来说就是读取之前的声明,并找到 #region AutoGenerate 和 #endregion
的位置,并根据c#类型,反射拿取特性标签的内容,拿到字段类型,再往文件插入相应的BinaryWriter,BinaryReader扩展方法。
public class AutoGenerate : Editor
{
private static string programPath = Application.dataPath;
[MenuItem("Pack/编译cs")]
static void GenerateCS()
{
foreach (var item in GenerateRegister.Glist)
{
GenerateOneCs(item);
}
}
private static void GenerateOneCs(GenerateInfo info) {
string fullPath = programPath+"/" + info.SavePath +"/" +info.name +".cs";
if (!File.Exists(fullPath)) {
Debug.LogErrorFormat("未找到cs文件:{0}",fullPath);
return;
}
string text;
using (StreamReader sr = new StreamReader(fullPath, System.Text.Encoding.UTF8))
{
text = sr.ReadToEnd();
}
text.Replace("\r\r\n","\r\n");
int startindex = text.IndexOf("#region AutoGenerate");
int endindex = text.IndexOf("#endregion", startindex);
if (startindex<=0 || endindex <=0) {
Debug.LogError("未找到#region AutoGenerate或#endregion :"+info.name);
return;
}
string beforeText = text.Substring(0,startindex+20);
string afterText = text.Substring(endindex);
int pos = startindex;
int spaceC = 0;
while (pos > 0) {
char c = text[pos];
if (c == '\n') break;
if (c == ' ') {
spaceC += 1;
}
if (c == '\t')
{
spaceC += 4;
}
pos--;
}
//
int indent = Mathf.FloorToInt(spaceC / 4f);
StringBuilder stringBuilder = new StringBuilder();
writeSerializeFunc(indent,info.t,info.GetFullProps(),stringBuilder);
writeDerializeFunc(indent,info.t,info.GetFullProps(),stringBuilder);
string endtext = beforeText + stringBuilder.ToString() + afterText;
using (StreamWriter swriteWriter = new StreamWriter(fullPath,false, System.Text.Encoding.UTF8))
{
swriteWriter.Write(endtext); // 向文件写入文本内容
}
}
private static void writeSerializeFunc(int indent,Type t, List<MemberInfo> members, StringBuilder builder) {
if (t.BaseType == null || !(typeof(ISerializeable).IsAssignableFrom(t.BaseType)))
{
builder.WriteOneLine(indent,"");
builder.WriteOneLine(indent, "public virtual void Serialize(System.IO.BinaryWriter writer)");
}
else {
builder.WriteOneLine(indent, "");
builder.WriteOneLine(indent, "public override void Serialize(System.IO.BinaryWriter writer)");
}
builder.WriteOneLine(indent, "{");
indent++;
foreach (var item in members)
{
GenerateSerialize(indent, item, builder);
}
indent--;
builder.WriteOneLine(indent, "}");
builder.WriteIndent(indent);
}
/// <summary>
/// 先这样后面有新类型再加
/// </summary>
/// <param name="indent"></param>
/// <param name="member"></param>
/// <param name="sb"></param>
public static void GenerateSerialize(int indent, MemberInfo member,StringBuilder sb) {
Type propertyType = member is FieldInfo ? (member as FieldInfo).FieldType : (member as PropertyInfo).PropertyType;
if (propertyType.IsEnum)
{
switch (GetEnumBaseType(propertyType))
{
case TypeCode.Int32:
sb.WriteOneLineFormat(indent, "writer.Write((int){0});", member.Name);
break;
case TypeCode.String:
sb.WriteOneLineFormat(indent, "writer.Write((String){0});", member.Name);
break;
default:
break;
}
return;
}
else if (propertyType.IsArray || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)))
{
sb.WriteOneLineFormat(indent, "writer.Write({0});", member.Name);
}
else if (propertyType.IsClass)
{
if (propertyType == typeof(string))
{
sb.WriteOneLineFormat(indent, "writer.Write({0});", member.Name);
}
else if (typeof(ISerializeable).IsAssignableFrom(propertyType))
{
sb.WriteOneLineFormat(indent, "writer.WriteSerializeable((ISerializeable){0});", member.Name);
}
else
{
Debug.LogError("未知的类型:" + propertyType.Name);
}
}
else {
sb.WriteOneLineFormat(indent, "writer.Write({0});", member.Name);
}
}
private static void writeDerializeFunc(int indent,Type t, List<MemberInfo> members, StringBuilder builder) {
if (t.BaseType == null || !(typeof(ISerializeable).IsAssignableFrom(t.BaseType )))
{
builder.WriteOneLine(indent,"");
builder.WriteOneLine(indent, "public virtual void Deserialize(System.IO.BinaryReader reader)");
}
else {
builder.WriteOneLine(indent, "");
builder.WriteOneLine(indent, "public override void Deserialize(System.IO.BinaryReader reader)");
}
builder.WriteOneLine(indent, "{");
indent++;
foreach (var item in members)
{
GenerateDeserialize(indent, item, builder);
}
indent--;
builder.WriteOneLine(indent, "}");
builder.WriteIndent(indent);
}
/// <summary>
/// </summary>
/// <param name="indent"></param>
/// <param name="member"></param>
/// <param name="sb"></param>
public static void GenerateDeserialize(int indent, MemberInfo member,StringBuilder sb) {
Type propertyType = member is FieldInfo ? (member as FieldInfo).FieldType : (member as PropertyInfo).PropertyType;
if (propertyType.IsEnum)
{
switch (GetEnumBaseType(propertyType))
{
case TypeCode.Int32:
sb.WriteOneLineFormat(indent, "this.{0} =({1})reader.ReadInt32();", member.Name, propertyType.Name);
break;
case TypeCode.String:
sb.WriteOneLineFormat(indent, "this.{0} = ({1})reader.ReadString();", member.Name, propertyType.Name);
break;
default:
break;
}
return;
}
else if (propertyType.IsArray || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)))
{
if (propertyType == typeof(int[]))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadIntArr();", member.Name);
}
else if (propertyType == typeof(string[]))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadStringArr();", member.Name);
}
else if (propertyType == typeof(double[]))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadDoubleArr();", member.Name);
}
else if (propertyType == typeof(bool[]))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadBooleanArr();", member.Name);
}
else if (propertyType == typeof(float[]))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadFloatArr();", member.Name);
}
else if (propertyType == typeof(long[]))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadLongArr();", member.Name);
}
else if (propertyType == typeof(List<int>))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadIntList();", member.Name);
}
else if (typeof(ISerializeable).IsAssignableFrom(propertyType.GetGenericArguments()[0]))
{
sb.WriteOneLineFormat(indent, "this.{0} = reader.ReadSerializeableList<T>();", member.Name, propertyType.Name);
}
}
else if (propertyType.IsClass)
{
if (propertyType == typeof(string))
{
sb.WriteOneLineFormat(indent, "{0} = reader.ReadString();", member.Name);
}
else if (typeof(ISerializeable).IsAssignableFrom(propertyType))
{
sb.WriteOneLineFormat(indent, "{0} = reader.ReadSerializeable<{1}>();", member.Name, propertyType.Name);
}
else
{
Debug.LogError("未知的类型:" + propertyType.Name);
}
}
else if (propertyType.Name == "Int32")
{
sb.WriteOneLineFormat(indent, "{0} = reader.ReadInt32();", member.Name);
}
else {
sb.WriteOneLineFormat(indent, "err", member.Name);
}
}
private static TypeCode GetEnumBaseType(Type type)
{
Type underlyingType = Enum.GetUnderlyingType(type);
if (underlyingType == typeof(int))
{
return TypeCode.Int32;
}
if (underlyingType == typeof(sbyte))
{
return TypeCode.SByte;
}
if (underlyingType == typeof(short))
{
return TypeCode.Int16;
}
if (underlyingType == typeof(long))
{
return TypeCode.Int64;
}
if (underlyingType == typeof(uint))
{
return TypeCode.UInt32;
}
if (underlyingType == typeof(byte))
{
return TypeCode.Byte;
}
if (underlyingType == typeof(ushort))
{
return TypeCode.UInt16;
}
if (underlyingType == typeof(ulong))
{
return TypeCode.UInt64;
}
if (underlyingType == typeof(bool))
{
return TypeCode.Boolean;
}
if (underlyingType != typeof(char))
{
throw new InvalidOperationException("InvalidOperation_UnknownEnumType");
}
return TypeCode.Char;
}
}