持久化是类型的一个核心特性,有时我们需要通过不同的方式传输和创建同一个对象,例如需要通过网络传输对象,或者需要将对象信息存储到文本文件或者XML文件中,这时,如何能够保持对象的状态,并在将来使用时,可以准确的还原到原来的状态,是非常重要的。
.NET可以使用序列化的方式来持久化对象,我们在可能的情况下,应该将类型定义为可以序列化的。序列化时,我们可以使用Serializable特性。
我们来看下面的简单示例,将对象信息存储到XML文件中。
首先定义两个可以序列化的类型。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 public class Employee
3 {
4 private PersonName m_Name;
5 public PersonName Name
6 {
7 get { return m_Name; }
8 set { m_Name = value; }
9 }
10
11 private string m_strAddress;
12 public string Address
13 {
14 get { return m_strAddress; }
15 set { m_strAddress = value; }
16 }
17
18 public Employee()
19 {
20 m_Name = new PersonName();
21 m_strAddress = string .Empty;
22 }
23
24 public Employee(PersonName name, string address)
25 {
26 m_Name = name;
27 m_strAddress = address;
28 }
29
30 public override string ToString()
31 {
32 return string .Format( " Name : {0} {1}, Address : {2} " , m_Name.FirstName, m_Name.LastName, m_strAddress);
33 }
34 }
35
36 [Serializable]
37 public class PersonName
38 {
39 private string m_strFirstName;
40 public string FirstName
41 {
42 get { return m_strFirstName; }
43 set { m_strFirstName = value; }
44 }
45
46 private string m_strLastName;
47 public string LastName
48 {
49 get { return m_strLastName; }
50 set { m_strLastName = value; }
51 }
52
53 public PersonName()
54 {
55 m_strFirstName = string .Empty;
56 m_strLastName = string .Empty;
57 }
58
59 public PersonName( string firstName, string lastName)
60 {
61 m_strFirstName = firstName;
62 m_strLastName = lastName;
63 }
64 }
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 {
3 Employee emp = new Employee( new PersonName( " Wing " , " Lee " ), " BeiJing " );
4 Console.WriteLine( " Ouput emp info before serialize: " );
5 Console.WriteLine(emp.ToString());
6
7 XmlSerializer serializer = new XmlSerializer( typeof (Employee));
8 StreamWriter writer = new StreamWriter( " emp.xml " );
9 serializer.Serialize(writer, emp);
10 writer.Close();
11 Console.WriteLine( " Serialization Success. " );
12
13 StreamReader reader = new StreamReader( " emp.xml " );
14 XmlSerializer desrializer = new XmlSerializer( typeof (Employee));
15 object o = desrializer.Deserialize(reader);
16 if (o is Employee)
17 {
18 Console.WriteLine( " Ouput emp info after deserialize: " );
19 Console.WriteLine((o as Employee).ToString());
20 }
21 }
可以看到序列化前和序列化后的对象信息,被完全一致的反应出来,这说明,序列化确实实现了对象的持久化。
有以下两个问题需要注意:
- 对于可以序列化的类型,必须提供没有参数的构造函数,上述代码中,如果Employee类型和PersonName类型没有显示的提供默认构造函数,那么程序在编译时,就会报错,提示在不提供没有参数的构造函数的情况下,无法执行序列化操作。
- 出于性能的考虑,我们可以不用将类型全部内容都置为可序列化。
C#在序列化时,可以采用上面代码中写的XmlSerializer的方式,也可以采用BinaryFormatter的方式,如果采用XmlSerializer的方式,那么NonSerialized特性是不会发挥作用的,同时这种方式允许序列化中的类型包含不能序列化的类型;但是对于BinaryFormatter来说,可以使用NonSerialized特性,同时进行序列化的类型所包含的其他所有类型,都必须是可序列化的,否则就会在序列化的过程中发生异常。
我们来看以下的代码,使用Formatter的正常方式。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 {
3 Employee emp = new Employee( new PersonName( " Wing " , " Lee " ), " BeiJing " );
4 Console.WriteLine( " Ouput emp info before serialize: " );
5 Console.WriteLine(emp.ToString());
6
7 BinaryFormatter serializer = new BinaryFormatter();
8 Stream stream = File.Open( " emp.txt " , FileMode.Create);
9 serializer.Serialize(stream, emp, null );
10 stream.Close();
11 Console.WriteLine( " Serialization Success. " );
12
13 stream = File.Open( " emp.txt " , FileMode.Open);
14 BinaryFormatter deserializer = new BinaryFormatter();
15 object o = deserializer.Deserialize(stream, null );
16 stream.Close();
17 if (o is Employee)
18 {
19 Console.WriteLine( " Ouput emp info after deserialize: " );
20 Console.WriteLine((o as Employee).ToString());
21 }
22 }
我们可以看到,对于使用了NonSerilized特性的字段来说,在反序列化后,新生成的对象中相应字段的值时null或者0,为了解决这个问题,我们可以实现IDeserializationCallBack接口,来对序列化后新生成的对象的字段进行初始化。
来看以下代码。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 public class Employee : IDeserializationCallback
3 {
4 private PersonName m_Name;
5 public PersonName Name
6 {
7 get { return m_Name; }
8 set { m_Name = value; }
9 }
10
11 [NonSerialized]
12 private string m_strAddress;
13 public string Address
14 {
15 get { return m_strAddress; }
16 set { m_strAddress = value; }
17 }
18
19 public Employee()
20 {
21 m_Name = new PersonName();
22 m_strAddress = string .Empty;
23 }
24
25 public Employee(PersonName name, string address)
26 {
27 m_Name = name;
28 m_strAddress = address;
29 }
30
31 public override string ToString()
32 {
33 return string .Format( " Name : {0} {1}, Address : {2} " , m_Name.FirstName, m_Name.LastName, m_strAddress);
34 }
35
36 public void OnDeserialization( object sender)
37 {
38 this .Address = " Vernus " ;
39 }
40 }
我们在编码的过程中,可能在将对象序列化后,又修改了类型的结构,这时,单纯采用Serialzable特性,是不能对修改进行区分的,甚至可能会引发异常。这时,我们可以实现ISerializable接口,来定制序列化过程。
我们来看下面的代码。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 public class Employee : IDeserializationCallback, ISerializable
3 {
4 private PersonName m_Name;
5 public PersonName Name
6 {
7 get { return m_Name; }
8 set { m_Name = value; }
9 }
10
11 [NonSerialized]
12 private string m_strAddress;
13 public string Address
14 {
15 get { return m_strAddress; }
16 set { m_strAddress = value; }
17 }
18
19 public Employee()
20 {
21 m_Name = new PersonName();
22 m_strAddress = string .Empty;
23 }
24
25 public Employee(PersonName name, string address)
26 {
27 m_Name = name;
28 m_strAddress = address;
29 }
30
31 private Employee(SerializationInfo info, StreamingContext context)
32 {
33 Name = info.GetValue( " name " , typeof (PersonName)) as PersonName;
34 Address = info.GetString( " address " );
35 }
36
37 public void GetObjectData(SerializationInfo info, StreamingContext context)
38 {
39 info.AddValue( " name " , Name);
40 info.AddValue( " address " , Address);
41 }
42
43 public void OnDeserialization( object sender)
44 {
45 this .Address = " Vernus " ;
46 }
47
48 public override string ToString()
49 {
50 return string .Format( " Name : {0} {1}, Address : {2} " , m_Name.FirstName, m_Name.LastName, m_strAddress);
51 }
52 }
一般情况下,我们对实现了ISerializable接口的类声明为sealed,这样可以免于被继承,因为继承后的子类,还要针对自身的数据情况,对GetObjectData()方法进行扩充,比较复杂。
编程时要注意,从序列化流中写入和读取数据的顺序必须一致。
总结:.NET框架为对象序列化提供了一个简单、标准的算法。如果我们的类型需要持久化,那就应该遵循标准的实现。如果我们不为类型添加序列化支持,那么其他使用我们类型的类也就不能支持序列化。我们所做的工作应该尽可能的使类型的使用者更加方便。如果可以,应该使用默认的方式来支持序列化;如果默认的Serializable特性不能够满足要求,则应该实现ISerializable接口。