一、序列化的引入、作用
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
序列化又称串行化,其目的是以某种存储形式使自定义对象持久化,或者将这种对象从一个地方传输到另一个地方。
所谓的序列化就是是将对象转换为容易传输的格式的过程,一般情况下转化为流文件,放入内存或者IO文件中。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象,或者和其它应用程序共享使用。反之,反序列化根据流重新构造对象。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为对象。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据。尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁 盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。公共语言运行时 (CLR) 管理对象在内存中的分布,.NET 框架则通过使用反射提供自动的序列化机制。对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次。.NET 框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。对对象图表的唯一要求是,由正在进行序列化的对象所引用的所有对象都必须标记为 Serializable(请参阅基 本序列化)。否则,当序列化程序试图序列化未标记的对象时将会出现异常。当反序列化已序列化的类时,将重新创建该类,并自动还原所有数据成员的值。
2) 在网络上传送对象的字节序列。
对象仅在创建对象的应用程序域中有效。除非对象是从MarshalByRefObject派生得到或标记为 Serializable,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。如果对象标记为 Serializable,则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送。如果对象是从MarshalByRefObject派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。也可以将从MarshalByRefObject派生得到的对象标记为Serializable。远程使用此对象时,负责进行序列化并已预先配置为SurrogateSelector的格式化程序将控制序列化过程,并用一个代理替换所有从MarshalByRefObject派生得到的对象。如果没有预先配置为SurrogateSelector,序列化体系结构将遵从下面的标准序列化规则.
二、.NET提供的三种序列化方式
.NET框架提供了三种串行化的方式:
1、是使用BinaryFormatter进行串行化;类中的所有成员变量(甚至标记为 private 的变量)都将被序列化
2、使用SoapFormatter进行串行化;类中的所有成员变量(甚至标记为 private 的变量)都将被序列化
3、使用XmlSerializer进行串行化。只序列化公共字段
第一种方式提供了一个简单的二进制数据流以及某些附加的类型信息,而第二种将数据流格式化为XML存储;第三种其实和第二种差不多也是XML的格式存储,只不过比第二种的XML格式要简化很多(去掉了SOAP特有的额外信息)。
1,2必须使用[Serializable]属性将类标志为可序列化的,3可以不用对类用[Serializable]属性进行标记
1,2可以序列化类中的所有成员变量(私有的,公有的),3只可以序列化类中的公有成员变量。
如果某个类的元素不想被序列化, 1,2可以使用[NonSerialized]属性来标志,3、可以使用[XmlIgnore]来标志。
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Xml.Serialization;
using System.Runtime.Serialization;
namespace SerializableTest
{
[Serializable]//表示类Book可以序列化
public class Book
{
public Book()
{
}
public string strBookName="null";
[NonSerialized]
public string strBookPwd="null";
private string _bookID="null";
[XmlIgnore]
public string BookID
{
get { return _bookID; }
set { _bookID = value; }
}
private string _bookPrice="null";
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);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Xml.Serialization;
namespace SerializableTest
{
class Program
{
static void Main(string[] args)
{
//创建类Book的一个实例book
Book book = new Book();
book.BookID = "1";
book.strBookName = "C#强化";
book.strBookPwd = "*****";
book.SetBookPrice("50.00");
//输出实例book的数据
book.Write();
//利用BinaryFormatter对实例book进行序列化和反序列化-----除了标记为NonSerialized的其他所有成员都能序列化
using (FileStream fs = new FileStream(@"c:\book.data", FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, book);//将对象序列化为给定流。
}
using (FileStream fs = new FileStream(@"c:\book.data", FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
Book book1 = (Book)formatter.Deserialize(fs);
Console.WriteLine("利用BinaryFormatter对实例book进行序列化和反序列化后的结果");
book1.Write();
}
///*利用SoapFormatter对实例book进行序列化和反序列化,输出结果和利用BinaryFormatter进行序列化和反序列化一样
// 使用之前需要应用System.Runtime.Serialization.Formatters.Soap.dll(.net自带的)
// 序列化之后的文件是Soap格式的文件(简单对象访问协议(Simple Object Access Protocol,SOAP),
// 是一种轻量的、简单的、基于XML的协议,它被设计成在WEB上 交换结构化的和固化的信息。
// SOAP 可以和现存的许多因特网协议和格式结合使用,包括超文本传输协议(HTTP),简单邮件传输协议(SMTP),
// 多用途网际邮件扩充协议(MIME)。它还支持从消息系统到远程过程调用(RPC)等大量的应用程序。
// SOAP使用基于XML的数据结构和超文本传输协议(HTTP)的组合定义了一个标准的方法来使用Internet上各种不同操作环境中的分布式对象。)*/
using (FileStream fs = new FileStream(@"c:\book1.data", FileMode.Create))
{
SoapFormatter formatter = new SoapFormatter();
formatter.Serialize(fs, book);//将对象序列化为给定流。
}
using (FileStream fs = new FileStream(@"c:\book1.data", FileMode.Open))
{
SoapFormatter formatter = new SoapFormatter();
Book book1 = (Book)formatter.Deserialize(fs);
Console.WriteLine("利用SoapFormatter对实例book进行序列化和反序列化后的结果");
book1.Write();
}
//利用 XmlSerializer 对实例book进行序列化和反序列化---xml序列化之后的文件就是一般的一个xml文件:
//采用xml序列化的方式只能保存public的字段和可读写的属性,对于private等类型的字段不能进行序列化
using (FileStream fs = new FileStream(@"c:\book2.data", FileMode.Create))
{
XmlSerializer formatter = new XmlSerializer(typeof(Book));
formatter.Serialize(fs, book);//将对象序列化为给定流。
}
using (FileStream fs = new FileStream(@"c:\book2.data", FileMode.Open))
{
XmlSerializer formatter = new XmlSerializer(typeof(Book));
Book book1 = (Book)formatter.Deserialize(fs);
Console.WriteLine("利用 XmlSerializer对实例book进行序列化和反序列化后的结果");
book1.Write();
}
}
}
}
分析:c:\book.data 保存的是二进制流,用记事本打开是乱码;
c:\book1.data 中的内容如下:
<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/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Book id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/SerializableTest/SerializableTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<strBookName id="ref-3">C#强化</strBookName>
<_bookID id="ref-4">1</_bookID>
<_bookPrice id="ref-5">50.00</_bookPrice>
</a1:Book>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
没有标记为NonSerialize 的字段strBookPwd
c:\book2.data 中的内容如下:
<?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>
</Book>
没有标记为XmlIgnore 的字段strBookId,和私有字段BookPrice
三、自定义序列化
如果你希望让用户对类进行串行化,但是对数据流的组织方式不完全满意,那么可以通过在自定义类中实现接口来自定义串行化行为。这个接口只有一个方法,GetObjectData. 这个方法用于将对类对象进行串行化所需要的数据填进SerializationInfo对象。你使用的格式化器将构造SerializationInfo对象,然后在串行化时调用GetObjectData. 如果类的父类也实现了ISerializable,那么应该调用GetObjectData的父类实现。
如果你实现了ISerializable,那么还必须提供一个具有特定原型的构造器,这个构造器的参数列表必须与GetObjectData相同。这个构造器应该被声明为私有的或受保护的,以防止粗心的开发人员直接使用它。
自定义序列化、反序列化有两点要求:
类要实现System.Runtime.Serialization 命名空间的 ISerializable 接口(当然要有[Serializable]属性)。
类要实现两个方法:一、ISerializable 接口的 void GetObjectData(SerializationInfo info, StreamingContext context). 用来处理要进行序列化的成员,其中info 参数提供各个成员字段信息,可在此时对对象中的成员赋值,调用AddValue()方法。
二、要实现一个构造函数 pubic classname(Serializationinfo info, StreamingContext context). 是构造函数是在反序列化时自动调用, 并利用info.GetStirng(GetDecimal )等函数获取值。
示例
将上述Book类修改如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Xml.Serialization;
using System.Runtime.Serialization;
namespace SerializableTest
{
[Serializable]//表示类Book可以序列化
public class Book
: ISerializable
{
public Book()
{
}
private Book(SerializationInfo info, StreamingContext ctxt)
{
strBookPwd = (String)info.GetValue("BookPwd", typeof(string));
strBookName = (String)info.GetValue("BookName", typeof(string));
_bookPrice = (String)info.GetValue("BookPrice", typeof(string));
}
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
info.AddValue("BookPwd", strBookPwd);
info.AddValue("BookName", strBookName);
info.AddValue("BookPrice", _bookPrice);
}
public string strBookName="null";
[NonSerialized]
public string strBookPwd="null";
private string _bookID="null";
[XmlIgnore]
public string BookID
{
get { return _bookID; }
set { _bookID = value; }
}
private string _bookPrice="null";
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);
}
}
}
运行结果如下:
c:\book1.data 的内容如下
<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/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Book id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/SerializableTest/SerializableTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<BookPwd id="ref-3">*****</BookPwd>
<BookName id="ref-4">C#强化</BookName>
<BookPrice id="ref-5">50.00</BookPrice>
</a1:Book>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
字段BookPwd可以被序列化,尽管被标记为NonSerialize。
c:\book1.data 的内容如下
<?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>
</Book>
因为字段 BookPrice是私有的,所以没有被序列化。