应用程序常常需要在硬盘上存储数据。本章前面介绍了逐段构建文本和数据文件,但这常常不是最方便的方式。有时最好以对象的形式存储数据。
.NET Framework在System.Runtime.Serialization和System.Runtime.Serialization.Formatters命名空间中提供了串行化对象的基础架构,这两个命名空间中的一些类实现了这个基础架构。Framework中有两个可用的实现方式:
● System.Runtime.Serialization.Formatters.Binary:这个命名空间包含了BinaryFormatter类,它能把对象串行化为二进制数据,把二进制数据串行化为对象。
● System.Runtime.Serialization.Formatters.Soap:这个命名空间包含了SoapFormatter类,它能把对象串行化为SOAP格式的XML数据,把SOAP格式的XML数据串行化为对象。
本章只介绍BinaryFormatter,因为还没有学习XML数据。但这两个类都实现了IFormatter接口,这里讨论的内容适用于这两个类。
IFormatter接口提供了如表22-8所示的方法。
表 22-8
|
方 法 |
说 明 |
|
void Serialize(Stream stream, object source) |
把source串行化为stream |
|
object Deserialize(Stream stream) |
并行化stream中的数据,返回得到的对象 |
重要的是,为了便于本章的讨论,这些方法都处理流,以便把这些方法与本章前面介绍的文件访问技术联系起来—— 可以使用FileStream对象。
所以,使用BinaryFormatter进行串行化非常简单:
IFormatter serializer = new BinaryFormatter();
serializer.Serialize(myStream, myObject);
并行化也很简单:
IFormatter serializer = new BinaryFormatter();
MyObjectType myNewObject = serializer.Deserialize(myStream) as MyObjectType;
显然,需要流和对象才能进行处理,但上面的代码对于所有的情况都是正确的。下面的示例将把这些代码用于实际。
试试看:对象的串行化
(1) 在C:\BegVCSharp\Chapter22目录下创建一个新的控制台应用程序ObjectStore。
(2) 给项目添加一个新类Product,修改代码,如下所示:
namespace ObjectStore
{
public class Product
{
public long Id;
public string Name;
public double Price;
[NonSerialized]
string Notes;
public Product(long id, string name, double price, string notes)
{
Id = id;
Name = name;
Price = price;
Notes = notes;
}
public override string ToString()
{
return string.Format("{0}: {1} (${2:F2}) {3}", Id, Name, Price, Notes);
}
}
}
(3) 把下面的代码放在Program.cs的顶部。需要导入System.IO命名空间,才能处理文件,导入其他命名空间,才能进行串行化:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
(4) 把下面的代码添加到Program.cs的Main()方法中:
static void Main(string[] args)
{
try
{
// Create products.
List<Product> products = new List<Product>();
products.Add(new Product(1, "Spiky Pung", 1000.0, "Good stuff."));
products.Add(new Product(2, "Gloop Galloop Soup", 25.0, "Tasty."));
products.Add(new Product(4, "Hat Sauce", 12.0, "One for the kids."));
Console.WriteLine("Products to save:");
foreach (Product product in products)
{
Console.WriteLine(product);
}
Console.WriteLine();
// Get serializer.
IFormatter serializer = new BinaryFormatter();
// Serialize products.
FileStream saveFile =
new FileStream("Products.bin", FileMode.Create, FileAccess.Write);
serializer.Serialize(saveFile, products);
saveFile.Close();
// Deserialize products.
FileStream loadFile =
new FileStream("Products.bin", FileMode.Open, FileAccess.Read);
List<Product> savedProducts =
serializer.Deserialize(loadFile) as List<Product>;
loadFile.Close();
Console.WriteLine("Products loaded:");
foreach (Product product in savedProducts)
{
Console.WriteLine(product);
}
}
catch (SerializationException e)
{
Console.WriteLine("A serialization exception has been thrown!");
Console.WriteLine(e.Message);
}
catch (IOException e)
{
Console.WriteLine("An IO exception has been thrown!");
Console.WriteLine(e.ToString());
}
Console.ReadKey();
}
(5) 运行应用程序,结果如图22-9所示。

图 22-9
(6) 修改Product.cs中的代码,如下所示:
namespace ObjectStore
{
[Serializable]
public class Product
{
...
}
(7) 再次运行应用程序,结果如图22-10所示。

图 22-10
(8) 在Notepad中打开Products.bin,文本如图22-11所示。

图 22-11
示例的说明
在这个示例中,创建了一个Product对象集合,把集合保存到磁盘上,然后重新加载它。但第一次运行应用程序时,抛出了一个异常,因为Product对象没有标记为“可串行化”。
.NET Framework要求把对象标记为可串行化,才能串行化它们。这有许多原因,包括:
● 一些对象串行化的效果不好。它们需要引用只有它们本身位于内存中时才存在的本地数据。
● 一些对象包含敏感的数据,这些数据不应以不安全的方式保存或传送到另一个进程中。
如本例所示,使用Serializable属性就可以把对象标记为可串行化:
namespace ObjectStore
{
[Serializable]
public class Product
{
...
}
这里要注意,这个属性并没有由派生类继承,它必须应用于要进行串行化的每个类。另外,用于生成Product对象集合的List<T>类有这个属性,否则,把它应用于Product就无益于使集合可串行化。
products集合成功串行化和并行化(在第二次尝试中)时,要注意另一个重要的问题。只重新构建了Id、Name和Price字段,这是因为使用了另一个属性NonSerialized。
[NonSerialized]
string Notes;
任何成员都可以用这个属性标记,但不能和其他成员一起存储,如果只有一个字段或属性包含敏感数据,这就是很有用的。
示例中还介绍了最后保存的数据。注意这里的一些数据是可以看懂的,这可能与我们期望的不同。BinaryFormatter类并没有试图把数据隐藏起来。当然,这里使用了流,在把它保存到磁盘上,或者加载并应用自己的加密算法时,很容易截取其中的数据。压缩也是这样――使用上一节中的技术,很容易在把对象数据保存到磁盘上时压缩它们。
串行化有许多内容,但前面介绍的基础知识已经足够了。下面要研究的一个较高级技术是使用ISerializable接口的定制串行化,它允许定制串行化的数据。在升级类,以便以后发布时,这是很重要的。修改可串行化的成员,会使保存的已有数据不再可读,除非提供自己的逻辑来保存和获取数据。





