Unity的序列化和反序列化过程解析
序列化是将对象转换为字节流的过程
序列化对象只需要调用格式化器BinaryFormatter的Serialize方法。Serialize方法到底是如何进行序列化的呢?首先,格式化器会参考目标对象的类型的元数据,进而了解要序列化的对象的信息。具体来说,使用了反射机制。
格式化器序列化的流程
以序列化一个英雄类Hero为例:
[Serializable]
public class Hero{
int maxHp;
int currentHp;
float Attack;
public Hero(){
...
}
public void Attack(){
...
}
}
第一步:获取目标对象的成员字段信息
首先调用FormatterServices类的GetSerializableMembers方法,该方法签名如下:
public static MemberInfo[]GetSerializableMembers(
Type type,
StreamingContext context
)
类型Type表示正在序列化的类型,此例中为Hero类,即获取一个类的元数据。元数据为描述数据的数据,即该类有哪些成员变量以及对应的类型是什么,例如Hero类有2个int型成员变量和一个float型成员变量。
SteamingContext参数表示发生序列化的上下文。
返回类型为MemberInfo[],MemberInfo对应可以被序列化的实例字段,本例中是int,int,float。
第二步:获取目标对象的成员字段的值
当获得了由MemberInfo对象所构成的数组后,就进入了对象被序列化的阶段。此时格式化器要调用FormatterServices类的另一个静态方法,即GetObjectData,该方法的签名如下所示。
public static Object[]GetObjectData(
Object obj,
MemberInfo[]members
)
该方法通过MemberInfo数组去类的实例obj中将对应的成员变量的实例提取出来,一一对应地生成至返回值Object数组中,返回值Object中的每个变量都是对象obj中对应的成员变量的实例。
第三步:程序集标识和类型的完整名称写入流
第四步:成员变量的值写入流
格式化器接下来会遍历在第一步和第二步得到的两个数组以获得成员名称和与其对应的值,最后将这些信息也写入流中。
格式化器反序列化的流程
基本与序列化的流程一一对应:
- 先从流中读取程序集标识和完整的类型名称;
- 然后为新对象obj分配一块内存空间,此时还没有调用构造函数;
- 再一次使用前文中提到的FormatterServices类的GetSerializableMembers方法(可见类的成员字段的信息并不需要写入到流中,而是动态地去获取),得到一个MemberInfo数组;
- 格式化器根据流中包含的数据创建一个Object数组;
- 根据MemberInfo数组和Object数组为对象obj进行初始化。
序列化和反序列化与程序集的关系
当代码在对对象进行序列化时,写入流的内容之中还包括类型的全名以及类型定义程序集的全名。而在反序列化时,格式化器首先获取的也是程序集的标识信息,然后再通过调用System.Reflection.Assembly的Load方法将目标程序集加载进入当前的AppDomain中。只有当程序集加载完成后,格式化器才能够在程序集中查找需要被反序列化的对象的类型信息。一旦找到符合要求的类型,接下来便是创建该类型的实例,然后再从流中获取和该实例字段相对应的值为该实例的字段赋值。
参考
《Unity 3D 脚本编程 使用C#语言开发跨平台游戏》第十章