序列化

     序列化这个东西,从很久以前就诞生了,以前很多编程语言中都有它的身影。所谓序列化,无非就是将程序内存中的数据,按照一定的组织形式,转化为顺序而连续的数据,以方便于网络传输和存储。随着XML的广泛应用,又出现了将数据转化为xml文档格式的序列化,我们这里称之为XML序列化。

本文中,将要讲述.Net编程中的普通序列化和XML序列化功能。其实对于.Net,有两种序列化方式:

1) 使用平台本身提供的自动序列化功能,此功能并不需要被序列化的类型实现任何接口或者编写任何更多的附加代码。
2) 通过实现接口来自定义序列化功能,在接口方法中写入代码来实现序列化。

    反射(Reflection)是.Net一个伟大的创举,这让我们的代码变得更加“智能”,它的出现也使得自动序列化变得可能。其实在大多数情况下,我们使用自动序列化已经足够满足我们的需求,但是可能在某些特殊情况下,我们需要实现相关的接口来完成自定义的序列化功能。

序列化是如何工作的

    为了让对序列化这个概念并不熟悉的读者有一个具体的认识,我写了下面的代码来演示一个使用序列化的程序如何工作的。
[Serializable]//该标签表示User类型可序列化 
class User
{
public string name;
public int age;
public override string ToString()
{
return "Name: " + name + "/tAge: " + age.ToString();
}
}
static void Main(string[] args)
{
//申明一个二进制序列化器
BinaryFormatter formatter = new BinaryFormatter();
FileStream fs = File.Open("User.dat", FileMode.Create);

User user=new User();//创建一个用户对象
user.name="张三";
user.age=20;

formatter.Serialize(fs,user);//序列化至文件
fs.Close();//关闭

fs = File.Open("User.dat", FileMode.Open);//打开文件
User user2 = formatter.Deserialize(fs) as User;//反序列化至内存对象
Console.Write(user2);//打印反序列化的结果
Console.ReadKey();
}

    运行结果:
   Name: 张三 Age: 20

    我们可以看到,BinaryFormatter(二进制序列化器)能够将user对象序列化到文件user.dat中,然后又通过反序列化将其从user.dat读取到user2对象中,整个过程中数据没有发生丢失或者改变。

    我们知道文件在逻辑上是一个字节跟着一个字节存储的,是一个顺序的存储方式,所以这样将内存中的数据转化为一个串行的数据流的过程,我们可以称之为“序列化”;而这一过程的逆过程,就叫做反序列化了。

   除了二进制序列化器之外,系统还提供了很多其他类型的Fomatter,比如我们在项目引用之中添加了System.Runtime.Serialization.Formatters.Soap之后,还可以找到一个SoapFormatter,专门用于将对象序列化为Web Service需要的Soap格式。当然,我们也可以书写自己的Formatter,只需要将自己的格式化器类型实现IFormatter接口就可以了。 

利用ISerializable进行自定义序列化

    上面那个例子中,是使用BinaryFormatter进行自动序列化,除了给User类增加了【Serializable】标签之外,没有书写任何代码,BinaryFormatter就自动将User类型的所有信息(公共字段和属性)进行了序列化和反序列化,这大大减少了我们进行代码开发的工作量,但是这种自动带来方便的同时,也使得我们无法对其序列化的过程进行任何干预和控制,而当我们想要对序列化的具体内容进行改变的时候,ISerializable就有它的用武之地了。

   下面是ISerializable的定义,但是我们发现它只定义了一个接口函数“GetObjectData”,这个是序列化的时候使用的,而进行反序列化的函数却没有定义,这是为什么呢?从MSDN中看到,原来是实现ISerializable的类型如果需要进行反序列化,必须还定义一个以SerializationInfo和StreamingContext为参数的公共构造函数,各种格式化器将使用这个公共的构造函数来反序列对象。

namespace System.Runtime.Serialization 
{
// Summary:
// Allows an object to control its own serialization and deserialization.
[ComVisible(true)]
public interface ISerializable
{
// Summary:
// Populates a System.Runtime.Serialization.SerializationInfo with the data
// needed to serialize the target object.
//
// Parameters:
// context:
// The destination (see System.Runtime.Serialization.StreamingContext) for this
// serialization.
//
// info:
// The System.Runtime.Serialization.SerializationInfo to populate with data.
//
// Exceptions:
// System.Security.SecurityException:
// The caller does not have the required permission.
void GetObjectData(SerializationInfo info, StreamingContext context);
}
}

    为了便于理解自定义序列化功能的必要性,我们这里给User类又增加了两个公共成员:temp和saveTime,其中temp是一个临时变量,并不需要进行序列化,以便节省网络带宽或者存储空间;而saveTime则标识这个对象上次被序列化的时间。这样一来,我们序列化的需求就发生了改变,一个成员不需要序列化,而另外一个成员序列化的值是需要在序列化的时候才能够决定的。

    下面的代码演示了如何实现自定义序列化的功能

[Serializable]//该标签表示User类型可序列化 
class User:ISerializable
{
public string name;
public int age;
public int temp=0;
public DateTime saveTime=DateTime.MinValue;
public override string ToString()
{
return "Name: " + name + "/tAge: " + age.ToString() +"/tTemp:"
+ temp.ToString() + "/tSaveTime:" + saveTime.ToString();
}


public User()
{

}
//这个公共构造函数必须在实现接口ISerializable之后被实现,否则将无法被反序列化
public User(SerializationInfo info, StreamingContext context)
{
//将各参数值取出来
name = info.GetString("name");
age = info.GetInt32("age");
saveTime = info.GetDateTime("saveTime");

}


#region ISerializable Members

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(User)); //设置对象类型
//添加序列化的数据
info.AddValue("name", name);
info.AddValue("age", age);
// Temp 不需要被序列化,所以我们并未将其增加到info
info.AddValue("saveTime", DateTime.Now); //saveTime取当前的时间,以便满足我们的需求
}

#endregion
}

XML序列化

    XML文档拥有诸多的优点,可以按照层次结构存储数据,可以直接手工读写,将数据序列化为XML文档显然更加适合网络传输和多个应用程序之间的交互;利用XML存储的配置文件也能够进行手工修改。不多说了,我们还是演示一下XML序列化是如何被实现的,下面的代码使用System.Xml.Serialization.XmlSerializer对上个例子里面的User类型进行序列化。

static void Main(string[] args) 
{
StringBuilder sb = new StringBuilder();
XmlWriter xw = XmlWriter.Create(sb);
XmlSerializer xs = new XmlSerializer(typeof(User));

User user = new User();//创建一个用户对象
user.name = "张三";
user.age = 20;
xs.Serialize(xw, user);

Console.Write(sb.ToString());//打印序列化产生的xml文档
Console.ReadKey();
}

输出结果是:

<?xml version="1.0" encoding="utf-16"?> 
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<name>张三</name>
<age>20</age>
<temp>0</temp>
<saveTime>0001-01-01T00:00:00</saveTime>
</User>


利用Attributes对XML序列化进行控制

    System.Xml.Serialization名字空间中定义了大量的Attributes来对XML的序列化过程进行干预。其中比较常用的有XmlRoot、 XmlElement、 XmlAttribute和XmlIgnore等等。下面我们对User类型的XML序列化进行一定的定制,将XML根节点设置为UserInfo,用户名字段通过Element方式进行存储,年龄字段通过XML属性方式进行存储,而Temp字段则不希望被序列化,我们来打上这些标签,看看有什么效果。

[XmlRoot("UserInfo")]//重新命名根节点 
public class User
{
[XmlElement("UserName")]//重新命名
public string name;
[XmlAttribute("Age")] //设置为属性
public int age;
[XmlIgnore] //不对Temp进行序列化
public int temp = 0;
public DateTime saveTime = DateTime.MinValue;
}

   运行结果:

<?xml version="1.0" encoding="utf-16"?> 
<UserInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Age="20">
<UserName>张三</UserName>
<saveTime>0001-01-01T00:00:00</saveTime>
</UserInfo>

    我们看到,通常利用Attributes标签,已经能让Xml序列化器按照我们的方式来进行序列化了,当然如果这些都无法满足你的要求的话,那么还有一种办法,那就是实现IXmlSerializable接口。
利用IXmlSerializable接口来对序列化的过程进行完全的控制
    IXmlSerializable接口的定义如下,其中ReadXml是用来进行反序列化的,而WriteXml则是进行序列化的,GetSchema函数可以不实现。

// Summary: 
// Provides custom formatting for XML serialization and deserialization.
public interface IXmlSerializable
{
XmlSchema GetSchema();
void ReadXml(XmlReader reader);
void WriteXml(XmlWriter writer);
}

   其使用方法很简单,这里不再赘述代码。在序列化的时候,只需在WriteXml方法中,将本对象的属性写入到writer之中;而反序列化的时候,就是在ReadXml方法中,将Xml中的数据读出,并且赋给各成员变量。 

序列化的对象不要循环引用

    假如我们我们在为User类型增加下面这样一个成员变量:

public User friend=null;

 
 

   并且将main函数中部分代码修改如下,会出现什么样的效果呢?

User user = new User();//创建一个用户对象 
user.name = "张三";
user.age = 20;


User user2 = new User();//创建一个用户对象
user2.name = "李四";
user2.age = 20;

user.friend = user2; //User2是User的朋友
user2.friend = user; //User也是User2的朋友

xs.Serialize(xw, user);//这里会报错

    显然序列user对象的时候会出现运行时错误,因为user字段中包含了user2,而user2的字段中又包含了user,这样序列化的时候就会形成循环,永无止境了。幸好我们的.Net Fraemwork相当的健壮,它并不会陷入到死循环中,但是会抛出一个异常,报告出这个错误。所以我们在使用序列化的时候,应该避免这种情况,我们可以采用如下方式:
为“Friend”字段加上[XmlIgnore]来使friend字段在序列化的过程中被忽略。
    如果我们确实需要friend的信息,我们可以为其增加一个新的属性叫做”FriendName”,并且将其包括在序列化中即可。

总结

    曾几何时,实现将运行时的数据保存到文件中并再读出来的功能是多么痛苦的一件事情,而制定专用的通信协议并且将需要的数据转化网络流也是多么伤透脑筋的事情,如今,dotNet平台已经给我们提供了强大的序列化器,是的我们再完成这些工作的时候事半功倍,无论你现在是否目前需要序列化的功能,好好地掌握它将会让你受益无穷。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值