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

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

    /// <summary>
    /// 将对象序列化为二进制数据 
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 将二进制数据反序列化
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    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属性,但是它提供了类似的功能。

    /// <summary>
    /// 将对象序列化为XML数据
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    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;
    }
    /// <summary>
    /// 将XML数据反序列化为指定类型对象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="data"></param>
    /// <returns></returns>
    public static T DeserializeWithXml<T>(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<bool> foldDic = new List<bool>();
    
    public virtual void OnBeforeSerialize()
    {
        fold = SerializeHelper.SerializeToBinary(foldDic);
    }

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

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

参考

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,861评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,263评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,033评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,999评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,000评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,483评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,850评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,827评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,366评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,404评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,525评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,130评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,853评论 3 338
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,293评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,426评论 1 276
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,082评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,590评论 2 366

推荐阅读更多精彩内容