将Voucher[] vouchers进行序列化时,可以看到Assembly和Type的信息只生成了一次,但是如果在vouchers中包含派生自Voucher的类的实例,也就是说vouchers是一个多态数组的时候,又是什么情况呢。如果BinnaryFormatter保存的是静态类型,那么反序列化时就不能完整恢复对象了。另一方面,因为BinaryFormatter.Serialize方法的参数是object类型,BinaryFormatter应当是保存了对象的动态类型的。为验证这一猜想,从Voucher类派生出BillVoucher。
BillVoucher代表从单据生成的凭证,它需要追踪原始的单据。BillVoucher类的定义如下:
namespace GeneralLedger
{
public class BillVoucher : Voucher
{
private string billId;
public BillVoucher(string billId, string voucherId, string creator, DateTime createTime) : base(voucherId, creator, createTime)
{
this.billId = billId;
}
public string BillId
{
get
{
return billId;
}
}
}
}
{
public class BillVoucher : Voucher
{
private string billId;
public BillVoucher(string billId, string voucherId, string creator, DateTime createTime) : base(voucherId, creator, createTime)
{
this.billId = billId;
}
public string BillId
{
get
{
return billId;
}
}
}
}
注意到BillVoucher并没有添加SerializableAttribute,这是因为一些Attribute是可以从基类继承的。那么SerializableAttribute能不能被继承呢,使用NUnit添加测试方法:
[Test]
public void TestSerializeBillVoucher()
{
BillVoucher voucher = new BillVoucher("2005020400001", "2005012900001", "xingd", DateTime.Now);
VoucherSerializer serializer = new VoucherSerializer();
serializer.Serialize("voucher.dat", voucher);
}
public void TestSerializeBillVoucher()
{
BillVoucher voucher = new BillVoucher("2005020400001", "2005012900001", "xingd", DateTime.Now);
VoucherSerializer serializer = new VoucherSerializer();
serializer.Serialize("voucher.dat", voucher);
}
运行NUnit进行测试,报错。会不是因为派生类多了一个字段呢?将billId暂时注释,运行测试报同样错误。由此可知,支持BinaryFormatter序列化的类必须添加SerializableAttribute,即便是其基类也支持BinaryFormatter序列化。为BillVoucher添加SerializableAttribute后,测试通过。
如果基类不支持序列化,而派生类添加了SerializableAttribute,又会发生什么样的情况呢?从实际应用来说,应该是不允许的,否则也就意味着任何可以派生的类都可以间接的实现序列化了。为了近一步验证,暂时将Voucher类的SerializableAttribute注释掉。运行NUnit测试TestSerializeBillVoucher,出错,提示如下:
GeneralLedger.VoucherTest.TestSerializeBillVoucher : System.Runtime.Serialization.SerializationException : The type GeneralLedger.Voucher in Assembly Voucher, Version=1.0.1861.37679, Culture=neutral, PublicKeyToken=null is not marked as serializable.
出错信息明确的说明了类Voucher没有标记为Serializable。
将Voucher的SerializableAttribute加上,接下来,我们开始测试多态数组的情况,接TestSerializeVouchers和TestDeserializeVouchers修改如下:
[Test]
public void TestSerializeVouchers()
{
Voucher[] vouchers = new Voucher[] {new Voucher("2005012900001", "xingd", DateTime.Now), new BillVoucher("2005020400001", "2005012900001", "xingd", DateTime.Now)};
VoucherSerializer serializer = new VoucherSerializer();
serializer.BatchSerialize("voucher.dat", vouchers);
}
[Test]
public void TestDeserializeVouchers()
{
VoucherSerializer serializer = new VoucherSerializer();
Voucher[] vouchers = serializer.BatchDeserialize("voucher.dat");
Assert.AreEqual(vouchers.Length, 2);
Assert.AreEqual(vouchers[0].VoucherId, "2005012900001");
Assert.AreEqual(vouchers[0].Creator, "xingd");
Assert.AreEqual(vouchers[1].GetType(), typeof(BillVoucher));
Assert.AreEqual(((BillVoucher)vouchers[1]).BillId, "2005020400001");
Assert.AreEqual(vouchers[1].VoucherId, "2005012900001");
Assert.AreEqual(vouchers[1].Creator, "xingd");
}
public void TestSerializeVouchers()
{
Voucher[] vouchers = new Voucher[] {new Voucher("2005012900001", "xingd", DateTime.Now), new BillVoucher("2005020400001", "2005012900001", "xingd", DateTime.Now)};
VoucherSerializer serializer = new VoucherSerializer();
serializer.BatchSerialize("voucher.dat", vouchers);
}
[Test]
public void TestDeserializeVouchers()
{
VoucherSerializer serializer = new VoucherSerializer();
Voucher[] vouchers = serializer.BatchDeserialize("voucher.dat");
Assert.AreEqual(vouchers.Length, 2);
Assert.AreEqual(vouchers[0].VoucherId, "2005012900001");
Assert.AreEqual(vouchers[0].Creator, "xingd");
Assert.AreEqual(vouchers[1].GetType(), typeof(BillVoucher));
Assert.AreEqual(((BillVoucher)vouchers[1]).BillId, "2005020400001");
Assert.AreEqual(vouchers[1].VoucherId, "2005012900001");
Assert.AreEqual(vouchers[1].Creator, "xingd");
}
运行后,两个测试都通过。生成的voucher.dat内容如下:
由voucher.data的内容,我们可以得出以下结论:
- BinaryFormatter序列化基于对象的动态类型。
- 派生类序列化时,基类的字段以base+field的方式作为标识。
- 对某一个object进行BinaryFormatter序列化时,由object所引用的对象所形成的Graph里,相同的Assembly只生成一份包含Assembly信息的输出,相同的Type也只生成一份包含类型信息的输出。
有意思的是,在00000060那一行,有一个看似多余的GeneralLedger.Voucher的字符串,猜测是用来记录vouchers的静态类型信息,也就是Voucher[]。Voucher[]的类型信息可能由Voucher的类型信息和一个数组的标识组成。在后面我们对序列化实现过程的生成文件格式分析的时候,会对这一猜测进行验证。有兴趣的朋友也可以自己验证一下。