C# - 序列化和反序列化

来源:互联网 发布:千元空气净化器 知乎 编辑:程序博客网 时间:2024/04/29 03:08

一、序列化的概念

序列化就是把一个对象保存到一个文件或数据库字段中去,反序列化就是在适当的时候把这个文件再转化成原来的对象使用。

      需要分清楚的概念:对象的序列化而不是类的序列化。对象的序列化表明C#提供了将运行中的对象(实时数据)写入到硬盘文件或者数据库中,此功能可以运用在需要保留程序运行时状态信息的环境下。

      使用序列化有两个最重要的原因:

一个原因是将对象的状态永久保存在存储媒体中,以便可以在以后重新创建精确的副本;

另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中。

      前提:要将对象的类声明为可以序列化。

 

最主要的作用有:

1、在进程下次启动时读取上次保存的对象的信息 

2、在不同的AppDomain或进程之间传递数据 

3、在分布式应用系统中传递数据

......

 

二、永久存储

通常需要将一个对象的各字段的值存储到磁盘中,这样以后可以检索这些数据。尽管不依赖序列化也可以很容易地做到这一点,但这样的方法通常十分麻烦并且容易出错,在您需要跟踪对象的层次结构时将变得越来越复杂。设想一下编写包含数以千计对象的大型商业应用程序,将不得不为每一对象编写代码以将字段和属性保存到磁盘上和从磁盘上还原它们,这是多么的复杂。而序列化为实现上述目标提供了一个方便的机制。

公共语言运行库管理对象在内存中的存储方式并通过使用反射提供自动的序列化机制。当序列化一个对象时,类的名称、程序集和类实例的所有数据成员都被写入存储中。对象通常在成员变量中存储对其他实例的引用。在序列化类时,序列化引擎跟踪已被序列化的引用对象,以确保同一对象不会被多次序列化。随 一起提供的序列化结构自动正确处理对象图和循环引用。对于对象图的唯一要求就是,由被序列化的对象引用的所有对象还必须标记为 Serializable。如果没有进行此标记,当序列化程序尝试序列化未标记的对象时,将引发一个异常。

当反序列化已序列化的类时,重新创建该类并且自动还原所有数据成员的值。

 

三、值封送

      对象只在创建它们的应用程序域中有效。将对象作为一个参数传递或将其作为结果返回的任何尝试都将失败,除非该对象派生自 MarshalByRefObject 或被标记为 Serializable。如果该对象被标记为 Serializable,该对象将被自动序列化,从一个应用程序域传输到其他的应用程序域,然后被反序列化以在第二个应用程序域中生成该对象的精确副本。此过程通常被称作值封送。

当对象从 MarshalByRefObject 派生时,从一个应用程序域将对象引用传递到另一个应用程序域,而不是传递该对象本身。还可将从 MarshalByRefObject 派生的对象标记为 Serializable。当该对象与远程处理一起使用时,负责序列化的格式化程序(该格式化程序已由代理选择器 SurrogateSelector 预先配置)控制序列化过程,并用代理代替从 MarshalByRefObject 派生的所有对象。如果没有适当的 SurrogateSelector,则序列化结构遵循在序列化过程的步骤中描述的标准序列化规则。

 

四、基本序列化

      使一个类可序列化的最简单方式是按如下所示使用 Serializable 属性标记。

[Serializable]

public class MyObject {

  public int n1 = 0;

  public int n2 = 0;

  public String str = null;

}

以下代码示例说明该类的实例是如何被序列化到一个文件中的。

MyObject obj = new MyObject();

obj.n1 = 1;

obj.n2 = 24;

obj.str = "Some String";

IFormatter formatter = new BinaryFormatter();

Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None);

formatter.Serialize(stream, obj);

stream.Close();

该示例使用二进制格式化程序执行序列化。您需要做的所有工作就是创建流的实例和您想要使用的格式化程序,然后对该格式化程序调用 Serialize 方法。要序列化的流和对象作为参数提供给该调用。尽管在此示例中并没有显式阐释这一点,但一个类的所有成员变量都将被序列化,即使是那些已标记为私有的变量。在此方面,二进制序列化不同于 XMLSerializer 类,后者只序列化公共字段。

将对象还原回其以前的状态十分简单。首先,创建用于读取的流和格式化程序,然后指示格式化程序反序列化该对象。下面的代码示例说明如何执行上述的操作。

IFormatter formatter = new BinaryFormatter();

Stream stream = new FileStream("MyFile.bin", FileMode.Open, FileAccess.Read, FileShare.Read);

MyObject obj = (MyObject) formatter.Deserialize(stream);

stream.Close();

 

// Here's the proof.

Console.WriteLine("n1: {0}", obj.n1);

Console.WriteLine("n2: {0}", obj.n2);

Console.WriteLine("str: {0}", obj.str);

上面所用的 BinaryFormatter 非常有效,生成了非常简洁的字节流。通过该格式化程序序列化的所有对象也可以通过该格式化程序进行反序列化,这使该工具对于序列化将在 .NET Framework 上被反序列化的对象而言十分理想。需要特别注意的是,在反序列化一个对象时不调用构造函数。出于性能方面的原因对反序列化施加了该约束。但是,这违反了运行库与对象编写器之间的一些通常约定,开发人员应确保他们在将对象标记为可序列化时了解其后果。

如果可移植性是必需的,则转为使用 SoapFormatter。只需用 SoapFormatter 代替上面代码中的 BinaryFormatter,并且如前面一样调用 Serialize Deserialize。此格式化程序为上面使用的示例生成以下输出。

 

<SOAP-ENV:Envelope

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xmlns:xsd="http://www.w3.org/2001/XMLSchema"

  xmlns:SOAP- ENC="http://schemas.xmlsoap.org/soap/encoding/"

  xmlns:SOAP- ENV="http://schemas.xmlsoap.org/soap/envelope/"

  SOAP-ENV:encodingStyle=

  "http://schemas.microsoft.com/soap/encoding/clr/1.0"

  "http://schemas.xmlsoap.org/soap/encoding/"

  xmlns:a1="http://schemas.microsoft.com/clr/assem/ToFile">

 

  <SOAP-ENV:Body>

    <a1:MyObject id="ref-1">

      <n1>1</n1>

      <n2>24</n2>

      <str id="ref-3">Some String</str>

    </a1:MyObject>

  </SOAP-ENV:Body>

</SOAP-ENV:Envelope>

需要特别注意的是,Serializable 属性不能被继承。如果我们从 MyObject 派生一个新类,此新类必须也用该属性标记,否则将无法被序列化。例如,当试图序列化下面的类的实例时,您将获得 SerializationException,通知您 MyStuff 类型没有标记为可序列化。

public class MyStuff : MyObject

{

  public int n3;

}

使用 Serializable 属性十分方便,但它具有上面所述的限制。在对类进行编译后,就不能再向该类添加序列化。

五、序列化举例

C#中常见的序列化的方法主要也有三个:

BinaryFormatter

SoapFormatter

XML序列化

本文就通过一个小例子主要说说这三种方法的具体使用和异同点:

这个例子就是使用三种不同的方式把一个Book对象进行序列化和反序列化,当然这个Book类首先是可以被序列化的。

 

Book

using System;

using System.Collections;

using System.Text;

 

namespace SerializableTest

{

    [Serializable]

    public class Book

    {

        public Book()

        {

            alBookReader = new ArrayList();

        }

 

        public string strBookName;

 

        [NonSerialized]

        public string strBookPwd;

 

        private string _bookID;

        public string BookID

        {

            get { return _bookID; }

            set { _bookID = value; }

        }

 

        public ArrayList alBookReader;

 

        private string _bookPrice;

        public void SetBookPrice(string price)

        {

            _bookPrice = price;

        }

 

        public void Write()

        {

            Console.WriteLine(/"Book ID:/" + BookID);

            Console.WriteLine(/"Book Name:/" + strBookName);

            Console.WriteLine(/"Book Password:/" + strBookPwd);

            Console.WriteLine(/"Book Price:/" + _bookPrice);

            Console.WriteLine(/"Book Reader:/");

            for (int i = 0; i < alBookReader.Count; i++)

            {

                Console.WriteLine(alBookReader[i]); [Page]

            }

        }

    }

}

这个类比较简单,就是定义了一些public字段和一个可读写的属性,一个private字段,一个标记为[NonSerialized]的字段,具体会在下面的例子中体现出来

 

1BinaryFormatter序列化方式

序列化,就是给Book类赋值,然后进行序列化到一个文件中

         Book book = new Book();

            book.BookID = /"1/";

            book.alBookReader.Add(/"gspring/");

            book.alBookReader.Add(/"永春/");

            book.strBookName = /"C#强化/";

            book.strBookPwd = /"*****/";

            book.SetBookPrice(/"50.00/");

            BinarySerialize serialize = new BinarySerialize();

            serialize.Serialize(book);  //2、反序列化

 

            BinarySerialize serialize = new BinarySerialize();

            Book book = serialize.DeSerialize();

            book.Write();   //3、测试用的

 

BinarySerialize

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

 

namespace SerializableTest

{

    public class BinarySerialize

    {

        string strFile = /"c:////book.data/";

 

        public void Serialize(Book book)

        {

            using (FileStream fs = new FileStream(strFile, FileMode.Create))

            {

                BinaryFormatter formatter = new BinaryFormatter();

                formatter.Serialize(fs, book);

            }

        }

 

        public Book DeSerialize()

        {

            Book book;

            using (FileStream fs = new FileStream(strFile, FileMode.Open))

            {

                BinaryFormatter formatter = new BinaryFormatter(); [Page]

                book = (Book)formatter.Deserialize(fs);

            }

            return book;

        }

    }

}

主要就是调用System.Runtime.Serialization.Formatters.Binary空间下的BinaryFormatter类进行序列化和反序列化,以缩略型二进制格式写到一个文件中去,速度比较快,而且写入后的文件已二进制保存有一定的保密效果。

调用反序列化后的截图如下:

 

也就是说除了标记为NonSerialized的其他所有成员都能序列化

 

2SoapFormatter序列化方式

调用序列化和反序列化的方法和上面比较类似,我就不列出来了,主要就看看SoapSerialize

SoapSerialize

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using System.Runtime.Serialization.Formatters.Soap;

 

namespace SerializableTest

{

    public class SoapSerialize

    {

        string strFile = /"c:////book.soap/";

 

        public void Serialize(Book book)

        {

            using (FileStream fs = new FileStream(strFile, FileMode.Create))

            {

                SoapFormatter formatter = new SoapFormatter();

                formatter.Serialize(fs, book);

            }

        }

 

        public Book DeSerialize()

        {

            Book book;

            using (FileStream fs = new FileStream(strFile, FileMode.Open))

            {

                SoapFormatter formatter = new SoapFormatter();

                book = (Book)formatter.Deserialize(fs);

 

           }

            return book;

        }

    }

}

主要就是调用System.Runtime.Serialization.Formatters.Soap空间下的SoapFormatter类进行序列化和反序列化,使用之前需要应用System.Runtime.Serialization.Formatters.Soap.dll.net自带的)

序列化之后的文件是Soap格式的文件(简单对象访问协议(Simple Object Access ProtocolSOAP),是一种轻量的、简单的、基于XML的协议,它被设计成在WEB上交换结构化的和固化的信息。 SOAP 可以和现存的许多因特网协议和格式结合使用,包括超文本传输协议(HTTP),简单邮件传输协议(SMTP),多用途网际邮件扩充协议(MIME)。它还支持从消息系统到远程过程调用(RPC)等大量的应用程序。SOAP使用基于XML的数据结构和超文本传输协议(HTTP)的组合定义了一个标准的方法来使用Internet上各种不同操作环境中的分布式对象。)

调用反序列化之后的结果和方法一相同

 

3XML序列化方式

调用序列化和反序列化的方法和上面比较类似,主要就看看XmlSerialize

XmlSerialize

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using System.Xml.Serialization;

 

namespace SerializableTest

{

    public class XmlSerialize

    {

        string strFile = /"c:////book.xml/";

 

        public void Serialize(Book book)

        {

            using (FileStream fs = new FileStream(strFile, FileMode.Create))

            {

                XmlSerializer formatter = new XmlSerializer(typeof(Book));

                formatter.Serialize(fs, book);

            }

        }

 

        public Book DeSerialize()

        {

            Book book;

            using (FileStream fs = new FileStream(strFile, FileMode.Open))

            {

                XmlSerializer formatter = new XmlSerializer(typeof(Book));

                book = (Book)formatter.Deserialize(fs);

            }

            return book;

        }

    }

}

从这三个测试类我们可以看出来其实三种方法的调用方式都差不多,只是具体使用的类不同

xml序列化之后的文件就是一般的一个xml文件:

book.xml

<?xml version=/"1.0/"?>

<Book xmlns:xsi=/"http://www.w3.org/2001/XMLSchema-instance/" xmlns:xsd=/"http://www.w3.org/2001/XMLSchema/">

  <strBookName>C#强化</strBookName>

  <strBookPwd>*****</strBookPwd>

  <alBookReader>

    <anyType xsi:type=/"xsd:string/">gspring</anyType>

    <anyType xsi:type=/"xsd:string/">永春</anyType>

  </alBookReader>

  <BookID>1</BookID>

</Book>

输出截图如下:

 

也就是说采用xml序列化的方式只能保存public的字段和可读写的属性,对于private等类型的字段不能进行序列化

 

关于循环引用:

比如在上面的例子Book类中加入如下一个属性:

        public Book relationBook;

在调用序列化时使用如下方法:

            Book book = new Book();

            book.BookID = /"1/"; [Page]

            book.alBookReader.Add(/"gspring/");

            book.alBookReader.Add(/"永春/");

            book.strBookName = /"C#强化/";

            book.strBookPwd = /"*****/";

 

            book.SetBookPrice(/"50.00/");

 

            Book book2 = new Book();

            book2.BookID = /"2/";

            book2.alBookReader.Add(/"gspring/");

            book2.alBookReader.Add(/"永春/");

            book2.strBookName = /".NET强化/";

            book2.strBookPwd = /"*****/";

            book2.SetBookPrice(/"40.00/");

 

            book.relationBook = book2;

            book2.relationBook = book;

            BinarySerialize serialize = new BinarySerialize();

            serialize.Serialize(book);

这样就会出现循环引用的情况,对于BinarySerializeSoapSerialize可以正常序列化(.NET内部进行处理了),对于XmlSerialize出现这种情况会报错:/"序列化类型 SerializableTest.Book 的对象时检测到循环引用。/"

 

 

 

、使用对象序列化,再反序列化回来就实现了内存的copy,实现对象的复制功能

/// <summary>

/// 把对象序列化并返回相应的字节

/// </summary>

/// <param name="pObj">需要序列化的对象</param>

/// <returns>byte[]</returns>

public  byte[] SerializeObject(object pObj)

{

       if(pObj == null)

              return null;

       System.IO.MemoryStream _memory = new System.IO.MemoryStream();

       BinaryFormatter formatter = new BinaryFormatter();

       formatter.Serialize(_memory,pObj);

       _memory.Position = 0;

       byte[] read = new byte[_memory.Length];

       _memory.Read(read,0,read.Length);

       _memory.Close();

       return read;

}

 

/// <summary>

/// 把字节反序列化成相应的对象

/// </summary>

/// <param name="pBytes">字节流</param>

/// <returns>object</returns>

public  object DeserializeObject(byte[] pBytes)

{

       object _newOjb = null;

       if(pBytes == null)

              return _newOjb;

       System.IO.MemoryStream _memory = new System.IO.MemoryStream(pBytes);

       _memory.Position = 0;

       BinaryFormatter formatter = new BinaryFormatter();

       _newOjb = formatter.Deserialize(_memory);

       _memory.Close();

       return _newOjb;

}