使用反射对任意对象进行二进制序列化的程序

背景

进行网络通信时,或者希望本地存储文件加密时,可以用二进制序列化。序列化时要把类的基本类型字段一个个序列化,遇到成员类要把成员类的基本类型成员也序列化。这样每个类都要有一个专门的序列化程序,这是令人难以接受的。需要封装一些代码,有不同的封装方案。

  1. 把各种基本数据类型的序列化封装成函数,把一个类序列化时按字段的顺序和类型手动调用这些函数。不过除了字符串麻烦点,其他类型序列化都只要一行;
  2. 写一个接收object的函数,使用GetFields()(反射)得到每个字段的类型,按类型序列化;

反射方案

还面临的问题是:

  1. 结果字节数组的长度是由所有字段的个数和类型决定的。需要对每个字段判断类型、对字符串确定长度后才能知道结果字节数组的长度。不可能预先知道结果字节数组的长度。
  2. 对于成员类,成员类还有成员类,成员类可能是列表或字典,列表或字典的元素还可能是类。而最终需要把所有结构分解为基本数据类型,这意味着需要递归调用;
  3. 要递归调用,就必须把一个字段的序列化写成函数FieldToBytes(),而不能直接写在ToBytes()的循环里。遇到成员类时,对成员类调用ToBytes();
  4. 对于类的列表,每个元素的字节数可能不同,把每个元素解析过之前无法知道它的字节数,也无法知道整个列表的字节数;
  5. 反序列化时是输入一个类的模板、一个字节数组,返回一个填入值的对象。类的信息可以通过1.泛型T,2.Type,3.object传入。返回类型T好像不行,没法把int、string转换成T,应该返回object。ToObject()里把类通过GetFields()得到字段们的fieldInfo,此时无法得到泛型参数T。所以反序列化不管是对类的解析还是对字段的反序列化,我们可能完全无法用泛型,因为无法从type获取T。

想到列表可以改变长度,可以先声明一个byte列表,把字段序列化后加到列表后面,全部加完后变成数组。

总之,

  1. 这里3个要素:A数据类,B数据类型,C字节数组,序列化是由A、B=>C,反序列化是B、C=>A。序列化时由一个数据类对象就知道AB了,不用专门提供B。
  2. 序列化和反序列化各需要2个函数,一个ToBytes()负责把object解析出字段,然后用循环处理,一个FieldToBytes()把单个字段根据type处理,遇到类则再调用ToObject()拆解,形成递归调用。
  3. 对列表字典的元素,调用FieldToObject(),而非ToBytes()。

反射容易混乱的2个概念

  1. Type;
  2. 类型名,也是泛型参数T;

typeof(T)可以得到type,它的逆运算是什么?deepseek回答,大意是无法由type得到相应的T输入给泛型方法,只能把这个方法改成输入type。

注意

  1. 非public的字段不会被序列化;
  2. 对null的处理:不要在字节数组直接跳过!反序列化时不会知道这个字段类是null,会解码错误,应该写入一个默认的,能代表无效的类对象。然而如果真的输入一个null给序列化程序,它自己也不可能通过类型实例化一个对象,GetType()会报错。那么不如序列化时发现字段位空就直接报错;

检查二进制数据正确性

二进制文件难读,但不是不能读。用Sublime Text打开,数据以16进制表示,也就是每2个数字是1个字节,这里每2个字节有一个空格,这里把空格隔开的数据叫一坨。一个int32是4个字节,就是这里的2坨。C#是数据内左边低位字节,也就是0900 0000的09是最低字节,这个数字是9。bool是1个字节,类里有bool会导致出现一个数据不是从空格开始的情况。

代码

支持列表、字典、嵌套类、类的列表字典、枚举。枚举反序列化时直接读取成int32输出。

using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System;
using System.Text;
using UnityEngine;

public class MyBinManager
{
    static MyBinManager instance = new MyBinManager();
    public static MyBinManager Instance => instance;
    string path
    {
        get { return $"{Application.persistentDataPath}/"; }
    }
    public byte[] ToBytes(object data)
    {
        List<byte> byteList = new List<byte>();
        Type dataType = data.GetType();
        FieldInfo[] fieldInfos = dataType.GetFields();
        for (int i = 0; i < fieldInfos.Length; i++)
        {
            byte[] bytes = FieldToBytes(fieldInfos[i].GetValue(data));
            byteList.AddRange(bytes);
        }
        return byteList.ToArray();
    }
    public byte[] FieldToBytes(object field)
    {
        byte[] bytes = null;
        if (field.GetType() == typeof(string))
        {
            string str = field as string;
            bytes = EncodeStr(str);
        }
        else if (field.GetType() == typeof(int))
        {
            bytes = BitConverter.GetBytes((int)field);
        }
        else if (field.GetType() == typeof(float))
        {
            bytes = BitConverter.GetBytes((float)field);
        }
        else if (field.GetType() == typeof(bool))
        {
            bytes = BitConverter.GetBytes((bool)field);
        }
        else if (field.GetType().IsEnum)
        {
            bytes = BitConverter.GetBytes((int)field);
        }
        else if (typeof(IList).IsAssignableFrom(field.GetType()))
        {
            IList iList = field as IList;
            // Type type = iList.GetType().GetGenericArguments()[0];
            bytes = ListToBytes(iList);
        }
        else if (typeof(IDictionary).IsAssignableFrom(field.GetType()))
        {
            IDictionary dictionary = field as IDictionary;
            bytes = DicToBytes(dictionary);
        }
        else
        {
#if UNITY_EDITOR
            Debug.Log(string.Concat("序列化成员类:", field.GetType()));
#endif
            bytes = ToBytes(field);
        }
        return bytes;
    }
    byte[] EncodeStr(string str)
    {
        byte[] strBytes = Encoding.UTF8.GetBytes(str);
        byte[] bytes = new byte[sizeof(int) + strBytes.Length];
        BitConverter.GetBytes(strBytes.Length).CopyTo(bytes, 0);
        strBytes.CopyTo(bytes, sizeof(int));
        return bytes;
    }
    byte[] ListToBytes(IList list)
    {
        List<byte> bytes = new List<byte>();
        bytes.AddRange(BitConverter.GetBytes(list.Count));
        for (int i = 0; i < list.Count; i++)
        {
            bytes.AddRange(FieldToBytes(list[i]));
        }
        return bytes.ToArray();
    }
    byte[] DicToBytes(IDictionary dic)
    {
        List<byte> bytes = new List<byte>();
        bytes.AddRange(BitConverter.GetBytes(dic.Count));
        foreach (object key in dic.Keys)
        {
            bytes.AddRange(FieldToBytes(key));
            bytes.AddRange(FieldToBytes(dic[key]));
        }
        return bytes.ToArray();
    }
    string DecodeStr(byte[] bytes, int start = 0)
    {
        int len = BitConverter.ToInt32(bytes, start);
        string result = Encoding.UTF8.GetString(bytes, start + sizeof(int), len);
        return result;
    }
    public object ToObject(byte[] bytes, Type type, ref int offset)
    {
        object temp = Activator.CreateInstance(type);
        Type dataType = temp.GetType();
        FieldInfo[] fieldInfos = dataType.GetFields();
        for (int i = 0; i < fieldInfos.Length; i++)
        {
            fieldInfos[i].SetValue(temp, FieldToObj(bytes, fieldInfos[i].FieldType, ref offset));
        }
        return temp;
    }
    object FieldToObj(byte[] bytes, Type type, ref int offset)
    {
        if (type == typeof(string))
        {
            int len = BitConverter.ToInt32(bytes, offset);
            offset += sizeof(int);
            string str = Encoding.UTF8.GetString(bytes, offset, len);
            offset += len;
            return str;
        }
        else if (type == typeof(int))
        {
            int result = BitConverter.ToInt32(bytes, offset);
            offset += sizeof(int);
            return result;
        }
        else if (type == typeof(float))
        {
            float result = BitConverter.ToSingle(bytes, offset);
            offset += sizeof(float);
            return result;
        }
        else if (type == typeof(bool))
        {
            bool result = BitConverter.ToBoolean(bytes, offset);
            offset += sizeof(bool);
            return result;
        }
        else if (type.IsEnum)
        {
            int result = BitConverter.ToInt32(bytes, offset);
            offset += sizeof(int);
            return result;
        }
        else if (typeof(IList).IsAssignableFrom(type))
        {
            IList list = BytesToList(bytes, type, ref offset);
            return list;
        }
        else if (typeof(IDictionary).IsAssignableFrom(type))
        {
            IDictionary dictionary = BytesToDic(bytes, type, ref offset);
            return dictionary;
        }
        else
        {
            return ToObject(bytes, type, ref offset);
        }

    }
    IList BytesToList(byte[] bytes, Type type, ref int offset)
    {
        IList iList = Activator.CreateInstance(type) as IList;
        int count = BitConverter.ToInt32(bytes, offset);
        offset += sizeof(int);
        Type eleType = type.GetGenericArguments()[0];
        for (int i = 0; i < count; i++)
        {
            iList.Add(FieldToObj(bytes, eleType, ref offset));
        }
        return iList;
    }
    IDictionary BytesToDic(byte[] bytes, Type type, ref int offset)
    {
        IDictionary iDic = Activator.CreateInstance(type) as IDictionary;
        int count = BitConverter.ToInt32(bytes, offset);
        offset += sizeof(int);
        Type keyType=type.GetGenericArguments()[0];
        Type valueType=type.GetGenericArguments()[1];
        for (int i = 0; i < count; i++)
        {
            object key = FieldToObj(bytes, keyType, ref offset);
            object value = FieldToObj(bytes,valueType,ref offset);
            iDic.Add(key,value);
        }
        return iDic;
    }
}

总结

这种方法在文件里完全没保存字段的名字,依赖类模板里的字段顺序反序列化,一旦模板类增加、减少了字段,之前的文件要么拿出来转换一下,否则全部作废。拓展性很差。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值