Unity和C#的序列化

1.概念

序列化简单讲就是把一种数据转换成另一种数据,持久化后在另一个地方反序列出相同的对象,在游戏开发中最常见就是协议数据序列化,配置数据序列化。

2.C#序列化

.NET框架提供了三种串行化的方式:1、是使用BinaryFormatter进行串行化;2、使用SoapFormatter进行串行化;3、使用XmlSerializer进行串行化。第一种方式提供了一个简单的二进制数据流以及某些附加的类型信息,而第二种将数据流格式化为XML存储;第三种其实和第二种差不多也是XML的格式存储,只不过比第二种的XML格式要简化很多(去掉了SOAP特有的额外信息)。

通常来说序列化和反序列化过程,是对某个对象的精确化拷贝(按值封送);但是,如果序列化对象是从MarshalByRefObject派生的子类,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身,也就是远程调用(具体看资料)。

1.序列化对象声明

[Serializable]标记要序列化的类,[Noserialized]标记类中不参与序列化的字段,类中的所有成员变量(甚至标记为 private 的变量)都将被序列化。需要注意的是,无法继承 Serializable 属性。如果从 MyClass 派生出一个新的类,则这个新的类也必须使用该属性进行标记,否则将无法序列化。

[Serializable]
public class MyClass
{
    [Noserialized]
    public string Temp;
    
    [field:Noserialized]    用于标识event不被序列
    public event EventHandler TempChanged;
}

2.BinaryFormatter

效率很高,能生成非常紧凑的字节流。需要注意的是,对对象进行反序列化时并不调用构造函数。

    /// 
    /// 将对象序列化为二进制数据 
    /// 
    /// 
    /// 
    public static byte[] SerializeToBinary(object obj)
    {
        MemoryStream stream = new MemoryStream();
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(stream, obj);

        byte[] data = stream.ToArray();
        stream.Close();

        return data;
    }

    /// 
    /// 将二进制数据反序列化
    /// 
    /// 
    /// 
    public static object DeserializeWithBinary(byte[] data)
    {
        MemoryStream stream = new MemoryStream();
        stream.Write(data, 0, data.Length);
        stream.Position = 0;
        BinaryFormatter bf = new BinaryFormatter();
        object obj = bf.Deserialize(stream);

        stream.Close();

        return obj;
    }

3.SoapFormatter

如果要求具有可移植性,请使用 SoapFormatter。所要做的更改只是将以上代码中的格式化程序换成 SoapFormatter,而 Serialize 和 Deserialize 调用不变。

4.XmlSerializer

假设我们需要XML,但是不想要SOAP特有的额外信息,那么我们应该怎么办呢?有两中方案:要么编写一个实现IFormatter接口的类;要么使用库类XmlSerializer,这个类不使用Serializable属性,但是它提供了类似的功能。

    /// 
    /// 将对象序列化为XML数据
    /// 
    /// 
    /// 
    public static byte[] SerializeToXml(object obj)
    {
        MemoryStream stream = new MemoryStream();
        XmlSerializer xs = new XmlSerializer(obj.GetType());
        xs.Serialize(stream, obj);

        byte[] data = stream.ToArray();
        stream.Close();

        return data;
    }
    /// 
    /// 将XML数据反序列化为指定类型对象
    /// 
    /// 
    /// 
    /// 
    public static T DeserializeWithXml(byte[] data)
    {
        MemoryStream stream = new MemoryStream();
        stream.Write(data, 0, data.Length);
        stream.Position = 0;
        XmlSerializer xs = new XmlSerializer(typeof(T));
        object obj = xs.Deserialize(stream);

        stream.Close();

        return (T)obj;
    }

5.序列化特性

可以控制序列化和反序列化过程,该过程中有几个回调,可以在序列化几个时期处理序列化数据。
OnDeserializedAttribute
OnDeserializingAttribute
OnSerializedAttribute
OnSerializingAttribute

using System;
using System.Runtime.Serialization;

[Serializable]
public class SerializableObject
{
    [OnSerializingAttribute]
    virtual protected void OnSerializing(StreamingContext context)
    {
    }

    [OnSerializedAttribute]
    virtual protected void OnSerialized(StreamingContext context)
    {
    }

    [OnDeserializingAttribute]
    virtual protected void OnDeserializing(StreamingContext context)
    {
    }

    [OnDeserializedAttribute]
    virtual protected void OnDeserialized(StreamingContext context)
    {
    }
}

6.自定义序列化

如果序列化特性不能满足需求,那就需要使用此接口来自定义化序列化操作。甚至可以序列化为另一个对象。继承了此接口后,序列化特性就不会生效了。

[Serializable]
public class Person : ISerializable
{
    public string FirstName;
    public string LastName;
    public string ChineseName;

    public Person()
    {
    }

    protected Person(SerializationInfo info, StreamingContext context)
    {
        FirstName = info.GetString("FirstName");
        LastName = info.GetString("LastName");
        ChineseName = string.Format("{0} {1}", LastName, FirstName);        
    }

    //这个方法用于将对类对象进行串行化所需要的数据填进SerializationInfo对象
    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("FirstName", FirstName);
        info.AddValue("LastName", LastName);
    }   
}

如果你实现了ISerializable,那么还必须提供一个具有特定原型的构造器,这个构造器的参数列表必须与GetObjectData相同。这个构造器应该被声明为私有的或受保护的,以防止粗心的开发人员直接使用它。

3.Unity序列化

在Unity中创建的资源数据都是序列化数据,最常见的场景、脚本、Prefab、材质等等。
比如可以显示在inspector中的:继承MonoBehaviour的脚本中public属性都自动被Unity序列化。


1.png

他们序列化成YAML格式 保存在硬盘上,如下:


2.png

1.MonoBehaviour

继承自MonoBehaviour的脚本可以挂在GameObject上,同时参与Unity序列化过程。当保存GameObject(保存Scene或者Prefab)时,Unity会把GameObject上所有的数据和场景信息或者Prefab一起保存在文件中;当创建GameObject时,Unity会把从场景文件或Prefab反序列化出所有GameObject和挂载的脚本以及数据。(所以通常来说,Unity的反序列化过程比较费时)。

2.ScriptableObject

把数据存在资源文件中,继承ScriptableObject,可以被放到.asset文件中,也就是说我们可以自定义asset的类型。Unity内置的asset资源有材质、贴图、音频等等,现在依靠ScriptableObject我们可以自定义新的资源类型,来存储我们自己的数据。

它可以和Unity其它资源一样,能在Inspector中编辑,能序列化,能打bundle,能在不同工程中使用,在编辑器模式可以用AssetDatabase访问到(它就是一个asset)。

具体使用方法可在文档中查看 Unity Doc

3.自定义序列化

在项目中遇到了一个问题:Unity序列化不支持多态,它把字段显示到Inspector,只会根据引用类型显示,不能正确显示真正引用的对象。
比如声明的引用类型是一个接口,Unity就不会序列化接口,但其实这个接口下可以接收几种子类对象,这样Unity就没法实现了。

可以自己写Editor代码正确显示对象,但在序列化时需要自己手动序列化数据。继承ISerializationCallbackReceiver接口可自定义序列化过程。

public interface ISerializationCallbackReceiver
    {
        // 这个方法是运行在Unity序列化前, 用来通知你Unity将准备序列化
        void OnAfterDeserialize();
        //这个方法是运行在Unity序列化后,用来通知你Unity将已经序列化完 
        void OnBeforeSerialize();
    }

后来我在项目中如下使用,一般用一个Unity可序列化的类型来接受数据

public abstract class BaseObjItem : MonoBehaviour, ISerializationCallbackReceiver
{
    [HideInInspector]
    public byte[] fold;

    [HideInInspector]
    public List foldDic = new List();
    
    public virtual void OnBeforeSerialize()
    {
        fold = SerializeHelper.SerializeToBinary(foldDic);
    }

    public virtual void OnAfterDeserialize()
    {
        foldDic = SerializeHelper.DeserializeWithBinary>(fold);
    }
}

数据本身是保存在fold属性中,当然如果要保存成json数据,那fold就可以为string数据类型。我这里保存成二进制格式了。

参考

C#序列化
Unity 序列化 总结
ScriptableObject的介绍
Unity序列化Doc

你可能感兴趣的:(Unity和C#的序列化)