C#序列化与反序列化

运行时序列化

序列化是将对象或对象图转换成字节流的过程。反序列化是将字节流转换回对象图的过程。

对象图:代表的是对象系统在特定时间点的一个视图(可以理解为快照)

应用例子:

  • 应用程序的状态(对象图)可轻松保存到磁盘文件或者数据库中,并在下次运行时恢复
  • 一组对象可以复制到系统的剪切板上,再粘回同一个或另一个应用程序
  • 一组对象可克隆 并放到一边作为备份;与此同时,用户操纵一组“主”对象
  • 一组对象可轻松地通过网络发送给另一台机器上运行的进程
  • 序列化成内存中的字节流后可以方便的进行加密和压缩
  • 用于数据持久化的一个主要手段

.NET Framework内建了出色的序列化和反序列化支持

24.1 序列化/反序列化快速入门

    [Serializable]
    internal static class QuickStart
    {
        public static void Main()
        {
            //创建对象图以便把它们序列化到流中
            var objGraph = new List[3];
            objGraph[0] = new List { "zhangsan", "lisi", "wanger", "mazi" };
            objGraph[1] = new List { "zhangsan2", "lisi2", "wanger2", "mazi2" };
            objGraph[2] = new List { "zhangsan3", "lisi3", "wanger3", "mazi3" };
            Stream stream = SerializeToMemory(objGraph);

            stream.Position = 0;
            objGraph = null;

            //反序列化对象
            objGraph = (List[])DeserializeFromMemory(stream);
            //objGraph = new List[3];
            //for (int i=0;i<3;i++)
            //{               
            //    objGraph[i] = (List)DeserializeFromMemory(stream);
            //}

            foreach (var s in objGraph) {
                foreach (var item in s)   Console.WriteLine(item);
                Console.WriteLine();
            }
            Console.Read();
        }

        private static MemoryStream SerializeToMemory(List[] objGraph)
        {
            //构造流来容纳序列化的对象
            MemoryStream stream = new MemoryStream();

            //构造序列化格式化器来执行所有真正的工作
            BinaryFormatter formatter = new BinaryFormatter();

            //告诉序列化格式化器将对象序列化到流中
            //foreach (var s in objGraph)
            //    formatter.Serialize(stream, s);
            formatter.Serialize(stream, objGraph);

            //将序列化好的对象返回给调用者
            return stream;
        }

        private static Object DeserializeFromMemory(Stream stream)
        {
            //构造序列化格式化器来做所有真正的工作
            BinaryFormatter formatter = new BinaryFormatter();

            //告诉格式化器从流中反序列化对象
            return formatter.Deserialize(stream);
        }
    }

FCL提供了两个格式化器:BinaryFormatter 和SoapFormatter(.NET Framework3.5开始已废弃 但可用于调试 因为能生成便于阅读的xml文本)

序列化

序列化对象图需要调用格式化器的Serialize方法,方法有两个参数:

​ 对流对象的引用,标识序列化好的字节放哪里(可以是System.IO.Stream的任何派生类的对象)

​ 对想要序列化的对象图的引用,可以是任何东西,对象图中的所有对象都被序列化到流中(利用反射查看每个对象中的实例字段,任何一个字段引用了其他对象,也会被序列化,算法会确保每个对象都只序列化一次,不出现死循环)

反序列化

DeserializeFromStream将流反序列化为对象图

格式化器的Deserialize方法会检查流的内容,构造流中所有对象的实例,并初始化所有字段使他们具有与当初序列化时相同的值

通常,要将Deserialize方法返回的对象引用转型成应用程序期待的类型

注意点

  • 由程序员保证代码为序列化和反序列化使用相同的格式化器(反例:BinaryFormatter序列化 、SoapFormatter反序列化)
  • 可将多个对象图序列化到一个流中,反序列化和序列化顺序一样
  • 序列化时,类型的全名和类型定义程序集的全名会被写入流(BinaryFormatter默认输出程序集的完整标识,包括程序集的文件名,版本号,语言文化以及公钥信息,反序列化时,格式化器会首先获取程序集标识信息,通过调用反射的Load方法确保程序集已加载到正在执行的AppDomain中)
    • 程序集加载好后,格式化器在程序集中查找和要反序列化的对象匹配的类型
    • 找不到匹配类型就抛出异常,并不再继续反序列化
    • 找到匹配的类型,就创建类型的实例并初始化用流中包含的值对其字段初始化
    • 类型中的字段与流中读取的字段名不完全匹配,就抛出SerializtionException异常,并不再继续反序列化

24.2 使类型可序列化

向类型应用特性System.SerializableAttribute

  • 序列化对象图时,任何对象不可序列化,格式化器Serialize都会抛出SerializtionException异常 (注意,格式化器是边序列化边检查对象是否可序列化,所以在抛出异常前可能已经有一部分对象序列化到流中,流中会包含已损坏的数据,需要从此中异常状态恢复,方法是先将对象序列化到一个MemoryStream中,如果都成功了,再复制到想要的目标流中)

  • SerializableAttribute特性只能应用于引用类型class,结构体struct,枚举类型enum和委托类型delegate

  • 枚举类型和委托类型总是可序列化的,不需显示应用特性,SerializableAttribute特性不会被继承(父类对象可被序列化,子类不应用特性的话子类对象也不能序列化)

重要提示:主要针对可拓展应用程序,自主挂接事件实现拿到程序集的名称,自己拼接路径,使用LoadFrom确保加载程序集、

24.3 控制序列化和反序列化

将SerializableAttribute特性应用于类型,所有的实例字段都会被序列化,但类型可能定义了一些不应被序列化的实例字段

  • 字段含有反序列化后变得无效的信息(例如,对象包含Windows内核对象(文件、进程、线程等)的句柄,反序列化后到另一个进程或另一台机器无意义了,因为Windows内核对象是跟进程相关的值)
  • 字段含有很容易计算的值,需要筛选出那些无须序列化的字段,减少传输的数据,增强应用程序的性能

例:

    [Serializable]
    internal class Circle
    {
        private double m_radius;

        [NonSerialized]
        private double m_area;

        public Circle(double radius)
        {
            m_radius = radius;
            m_area = Math.PI * m_radius * m_radius;
        }

        [OnDeserialized]
        private void OnDeserialized(StreamContent context)
        {
            m_area = Math.PI * m_radius * m_radius;
            //反序列化后调用
        }

        [OnDeserializing]
        private void OnDeserializing(StreamContent context)
        {
            //反序列化前调用
        }

        [OnSerialized]
        private void OnSerialized(StreamContent context)
        {
            //序列化后调用
        }

        [OnSerializing]
        private void OnSerializing(StreamContent context)
        {
            //序列化前调用
        }
    }

注意:

  • 使用这4个属性中的任何一个,方法应将声明为private,以免他被普通代码调用,格式化器运行时有足够的安全权限,能够调用私有方法。
  • 反序列化期间,当格式化器看到类型提供的一个方法标记了OnDeserialized特性时,格式化器会将这个对象的引用添加到一个内部列表中。所有对象都反序列化后,格式化器反向遍历列表,调用每个对象的OnDeserialized方法。之所以这样以相反的顺序调用,是因为这样才能使内层对象先于外层对象完成反序列化
  • 如果序列化类型的实例,在类型中添加新字段,然后试图反序列化不包含新字段的对象,格式化器会抛出SerializtionException异常,并显示流中要反序列化的数据包含错误的成员数目。 当我们必须要这么做的时候,我们需要为类型中新增的每个字段应用OptionalFieldAttribute特性,然后格式化器看到该特性应用于一个字段时,就不会因为流中的数据不包含这个字段而抛异常

更新:
工作中需要,所以自己写了一个序列化成Json格式以及压缩成流的工具类,具体看博文
https://blog.csdn.net/qq_43565708/article/details/134512162?spm=1001.2014.3001.5501

你可能感兴趣的:(c#,开发语言,.net,windows)