上一篇文章讨论过,控制序列化和反序列化过程的最佳方式就是使用以下特性:
OnSerializing
OnSerialized
OnDeserializing
OnDeserialized
NonSerialized
OptionalField
使用这些特性是基于反射技术完成的,性能有一点损伤。还有在极少数的情况下,这些特性不能提供你想要的全部控制。你的类型通过实现ISerializable接口,也能支持运行时序列化,该接口定义如下:
public interface ISerializable
{
/// <summary>Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with the data needed to serialize the target object.</summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to populate with data. </param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization. </param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
[SecurityCritical]
void GetObjectData(SerializationInfo info, StreamingContext context);
}
这个接口只有一个GetObjectData方法,通过它可以把对象序列化到数据流。实现这个接口的同时还应该实现一个特殊的构造器,通过这个特殊的构造器可以把数据流反序列化到对象。这个特殊构造器签名:
private ClassXXX(SerializationInfo info, StreamingContext context)
{
...
}
快速入门:
[Serializable]
public sealed class Product : ISerializable
{
private string _name;
public string Name
{
get { return this._name; }
set { this._name = value; }
}
public Product() { }
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Product(SerializationInfo info, StreamingContext context)
{
//_name = (String)info.GetValue("Name", typeof(string));
_name = (String)info.GetString("Name");
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
//info.AddValue("Name", _name, typeof(string));
info.AddValue("Name", _name);
}
}
var formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
var product = new Product { Name = "Hello" };
formatter.Serialize(stream, product);
stream.Position = 0;
var product2 = (Product)formatter.Deserialize(stream);
重要提示:
1. 某类型一旦实现ISerializable接口,它的所有的派生类型也必须实现它,而且派生类必须保证调用基类的GetObjectData方法和特殊构造器。
2. 某类型一旦实现ISerializable接口,便不能删除它,否则会失去与派生类型的兼容性。所以密封类实现ISerializable接口是最让人放心的。
3. ISerializable接口和特殊构造器是给格式化器调用,其他代码不应该调用它们。类型在实现ISerializable接口的时候,应该使用显式实现。
4. 某类型在定义特殊构造器的时候,如果类型是密封类,那么它的特殊构造器应该声明成private的。否则应该声明成protected的,将来需要给它的子类的特性构造器调用。
5. 为ISerializable接口和特殊构造器添加如下特性,以防止其他代码调用它们:
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
格式化器序列化对象图时,会检查每一个对象。如果发现对象的类型实现了ISerializable接口,就会忽略所有的定制特性,改为构造新的SerializationInfo对象。该对象包含了需要序列化的对象的字段的集合。
构造SerializationInfo对象时,格式化器要提供两个参数:Type和IFormatterConverter。
Type参数代表了要序列化的对象的类型。通过Type参数,可以得到类型的全名(Type.FullName)和程序集标识(Type.Module.Assembly.FullName)。
可以访问SerializationInfo对象的FullTypeName属性和AssemblyName属性得到它们。如果想要修改这两个属性,可以调用SerializationInfo对象的SetType方法完成。
构造好并初始化好SerializationInfo对象之后,格式化器调用类型的GetObjectData方法,向它传递SerializationInfo对象的引用,GetObjectData方法决定需要哪些信息来序列化对象,并将这些信息添加到SerializationInfo对象中,SerializationInfo类型提供了众多重载AddValue方法,我们想序列化哪个字段就对这个字段调用一次AddValue方法。GetObjectData方法添加好所有必要的序列化信息之后,会返回格式化器。现在,格式化器获取已添加到SerializationInfo对象的所有值,并把它们都序列化到流中。
完成了序列化工作之后,再来看反序列化,格式化器从流中提取一个对象时,会为新对象分配内存,通过调用FormatterServices.GetUninitializedObject方法,然后格式化器会检查类型是否实现了ISerializable接口,如果存在这个接口,格式化器就尝试调用那个特殊构造器。它的参数和GetObjectData方法完全一致。构造器获取SerializationInfo对象引用,在这个对象中,包含了对象序列化时添加的所有值,特殊构造器可调用GetXXX等任何一个方法(XXX是具体的数据类型比如Char,Int16,Int32等等),向它传递与序列化同一字段对应的字符串,可以返回流中的字段的值,并用返回值初始化新对象的各个字段。
反序列化对象的字段时,应调用和对象序列化时传给AddValue方法的值的类型匹配的Get方法。比如在GetObjectData方法调用AddValue方法传递的是Int32值,那么在反序列化对象时,应该为同一值调用GetInt32方法。如果值在流中的类型和你试图获取的Get方法类型不符,格式化器会尝试调用IFormatterConverter接口将流中的值转型为你制定的类型。格式化器会负责构造SerializationInfo对象,但是格式化器没有提供任何方式让你选择不同的IFormatterConverter类型,FCL提供了IFormatterConverter接口的默认实现FormatterConverter类。在FormatterConverter类中,调用了Convert类的各种静态方法在不同的核心类型之间对值进行转换,比如将Int32转换成一个Int64. 然而,为了能在其他任意类型之间转换,FormatterConverter类要调用Convert的ChangeType方法,将序列化好的类型转型为一个IConvertible接口,再调用恰当的接口方法。所以要运行一个可序列化类型的对象反序列化成一个不同的类型,可以考虑让自己的类型实现IConvertible接口。
特殊构造器也可以不调用上面的GetXXX方法,而是调用GetEnumerator。该方法返回SerializationInfoEnumerator对象,可用该对象遍历SerializationInfo对象中包含的所有值。枚举的每一个值都是一个SerializationEntry对象。
对于已经实现了ISerializable接口和特殊构造器的类型,当我们定义它的派生类的时候,必须保证调用基类的GetObjectData方法和特殊构造器。否则对象是不能正确序列化和反序列化的。如果你的派生类中没有任何额外的字段,因而没有特殊的序列化/反序列化需求,就完全不必实现ISerializable。接口的GetObjectData方法是virtual的,特殊构造器也是virtualized的,在反序列期间,格式化器会检查要实例化的类型,如果那个类型没有提供特殊构造器,格式化器会扫描基类,直到找到实现了特殊构造器的类。
重要提示:
特殊构造器中的代码一般从传递给它的SerializationInfo对象中提取字段,提取好字段之后,并不能保证对象已经完全反序列化好了,尤其当对象内部引用另一个类型的字段。如果这个时候你想调用对象的成员,建议你的类型提供一个应用了OnDeserialized特性的方法,或者让类型实现IDeserializationCallback接口的OnDeserialization方法。构造器在调用这两个方法的时候,对象的所有字段应该都已经设置好了。对于对象图来说,它们的OnDeserialized方法的调用顺序是没有保障的,所以虽然字段可能已初始化好,但是仍然不能确定被引用的对象是否已完全发序列化好。
要实现ISerializable但基类没有实现怎么办?
ISerializable接口功能非常强大,运行类型可以完全控制如何对类型的实例进行序列化和反发序列化。现在,当派生类实现ISerializable接口的时候,还要负责它的基类的字段的序列化,如果基类已经实现了ISerializable接口,那么对基类的字段进行序列化就非常简单,调用基类的GetObjectData方法。但是当基类没有实现ISerializable接口的时候,派生类必须手动序列化基类的字段,具体的做法是获取它们的值,并把这些值添加到SerializationInfo对象里。当反序列化的时候,在特殊构造器中,还必须从SerializationInfo对象中取出这些值,并以某种方式设置基类的字段。如果基类的字段是public或protected的,那么一切都很容易实现。如果是private字段,就没法实现。
示例代码:
[Serializable]
class MyBase
{
protected string _name = "Nestor";
public MyBase() { }
}
[Serializable]
class MyDerived : MyBase, ISerializable
{
private DateTime _dateTime = DateTime.Now;
public MyDerived() { }
//如果这个构造器不存在,便会引发一个SerializationException异常
//如果这个类不是密封类,这个构造器就应该是protected的
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private MyDerived(SerializationInfo info, StreamingContext context)
{
Type baseType = this.GetType().BaseType;
MemberInfo[] memberInfos = FormatterServices.GetSerializableMembers(baseType, context);
for (int i = 0; i < memberInfos.Length; i++)
{
FieldInfo fieldInfo = (FieldInfo)memberInfos[i];
string serializedName = baseType.FullName + "+" + fieldInfo.Name;
var fieldVal = info.GetValue(serializedName, fieldInfo.FieldType);
fieldInfo.SetValue(this, fieldVal);
}
_dateTime = info.GetDateTime("Date");
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Date", _dateTime);
Type baseType = this.GetType().BaseType;
MemberInfo[] memberInfos = FormatterServices.GetSerializableMembers(baseType, context);
for (int i = 0; i < memberInfos.Length; i++)
{
FieldInfo fieldInfo = (FieldInfo)memberInfos[i];
string serializedName = baseType.FullName + "+" + fieldInfo.Name;
var fieldVal = fieldInfo.GetValue(this);
info.AddValue(serializedName, fieldVal);
}
}
public override string ToString()
{
return string.Format("Name={0}, Date={1}", _name, _dateTime);
}
}
var formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
MyDerived myDerived = new MyDerived();
formatter.Serialize(stream, myDerived);
stream.Position = 0;
var myDerived2 = (MyDerived)formatter.Deserialize(stream);
文章回顾:
实现ISerializable接口,也能支持运行时序列化
特殊的构造器可以把数据流反序列化到对象
ISerializable接口实现和基类之间的关系