代码改变世界

Effective C# 学习笔记(二十七)使你的类型可被序列化

2011-07-16 11:12  小郝(Kaibo Hao)  阅读(538)  评论(0编辑  收藏  举报

.net中提供了类型对象序列化的简便实现方式,类型的持久性是一个类型的核心概念,为了使你的类的消费者在使用你的类型对象作为属性或使用你的类型作为父类时,能够方便的序列化他们创建的新类型,你应该尽可能的使你的类型可被序列化。否则这些可被序列化的特性的实现就要交给你的那些类型的消费者了,可惜他们并不比你更了解你的类型。所以使你的类型可被序列化是你应尽的义务和责任…...

一、序列化的对循环引用的处理方式

.NET Serialization 在输出流中保存了你对象中的所有变量,并且其支持任意对象的关联关系,甚至在对象间存在循环引用时,其也只会为每一个对象保存还原一个Copy。

 

二、[Serializable]的使用方法

注意:一个可被序列化的对象,其所有属性及属性关联的属性应都是可被序列化的。.NET中,你可以使用[Serializable]属性来声明一个类型是可被序列化的,见如下代码:

[Serializable]

public class MyType

{

private string label;

private int value;

private OtherClass otherThing; //注意这里的OtherClass在可被序列化时Mytype才是可被序列化的,若OtherClass不可被序列化,则MyType也就不可被序列化了

}

另外需要说明的是:[Serializable]属性支持二进制和SOAP序列化

三、使用NonSerializable属性声明不被序列化的属性,实现IDeserialization接口实现自定义初始化逻辑(注意自定义初始化的执行顺序)

你可以使用[NonSerialized]属性来声明该属性在序列化时,不被序列化,这样某些只被用来作为临时计算结果的变量就不用被序列化回去了。代码如下:

[Serializable]

public class MyType

{

private string label;

[NonSerialized]

private int cachedValue;

private OtherClass otherThing;

}

这里被[NonSerialized]属性声明的对象的默认值则被赋为0或Null。若0或null不是符合你业务逻辑的合理值,你可以让该对象的所属类型来实现IDeserializationCallback接口,实现OnDeserializaiton方法,来保证你的属性值初始化为符合业务逻辑的值。但是这个OnDeserialzaition方法的调用时机并不是像你想像的那样完美,其会在整个对象被读取后调用。也就是说整个对象的各个属性此时已可访问该未被OnDerialization执行处理的对象。这时你就需要注意整个对象内部各个属性之间的调用关系和顺序了。

四、实现ISerializable接口处理旧版本的序列化对象

使用序列化,会使你的代码有一天处理旧版本的序列化对象,这样的对象和你的新类型会存在属性上的个数上差异,这时代码会抛出异常。这时你需要使用ISerializable接口来控制这些异常。也就是说当你需要为[Serializable]标记的类型添加序列化扩展的功能时,请使用ISerializable接口来实现。其与默认的序列化的实现的属性和方法是一致的。

在实现 ISerializable接口时,你应该实现一个叫做GetObjectData()方法,以实现将对象数据写入到一个流中(即序列化的过程),并且你应该提供一个序列化的构造器去将流中的数据转换为对象(即反序列化的过程)。其形参列表如下:

//注意这里的构造器的修饰符为private,这里控制了该构造器只被Serialization framework使用

private MyType(SerializationInfo info,StreamingContext cntxt)

五、不可被继承和可被继承类的自定义ISerializable实现的方法

对于类的序列化扩展有两种情景,一种是不可被继承的叶子类型(即被sealed关键字修饰的类型),这种类型的序列化定制化较后者简单,不需要为子类的继承行为担心;还有一种是可被继承的类型,这种类型应该提供protected关键字修饰的序列化构造器,并且在GetObjectData()方法中添加子类可重载部分的调用,如可以创建一个修饰virtual的方法供子类重载,这样就保证了系统调用GetObjectData()中不会忘记调用子类的序列化方法调用了,如下面第二例中

父类中声明的 protected virtual void WriteObjectData(SerializationInfo inf,StreamingContext cxt)

。还要注意在第二种情况下,若是该可被继承类为abstract类,则需要将GetObjectData()方法声明为abstract方法。对于此两种类型的ISerializable实现如下:

  1. 不可被继承的Leaf 类 (被sealed关键字修饰的类)

//注意这里引入两个命名空间,主要是控制在序列化流中的数据不被非法代码篡改

using global::System.Runtime.Serialization;

using global::System.Security.Permissions;

[Serializable]

//这里用sealed关键字声明该类型不可被再继承

public sealed class MyType : ISerializable

{

private string label;

[NonSerialized]

private int value;

private OtherClass otherThing;

private const int DEFAULT_VALUE = 5;

private int value2;

// public constructors elided.

// Private constructor used only

// by the Serialization framework.

private MyType(SerializationInfo info,

StreamingContext cntxt)

{

label = info.GetString("label");

otherThing = (OtherClass)info.GetValue("otherThing",

typeof(OtherClass));

try

{

value2 = info.GetInt32("value2");

}

catch (SerializationException)

{

// Found version 1.

value2 = DEFAULT_VALUE;

}

}

//这里的声明控制了序列化流的写入操作不可被篡改

[SecurityPermissionAttribute(SecurityAction.Demand,

SerializationFormatter = true)]

void ISerializable.GetObjectData(SerializationInfo inf,

StreamingContext cxt)

{

inf.AddValue("label", label);

inf.AddValue("otherThing", otherThing);

inf.AddValue("value2", value2);

}

}

  1. 可被继承的类

//父类的写法

using global::System.Runtime.Serialization;

using global::System.Security.Permissions;

[Serializable]

public class MyType : ISerializable

{

private string label;

[NonSerialized]

private int value;

private OtherClass otherThing;

private const int DEFAULT_VALUE = 5;

private int value2;

// public constructors elided.

// Protected constructor used only by the

// Serialization framework.

//这里用projected关键字修饰了父类的序列化构造器

protected MyType(SerializationInfo info,

StreamingContext cntxt)

{

label = info.GetString("label");

otherThing = (OtherClass)info.GetValue("otherThing",

typeof(OtherClass));

//value2为新增的属性,对于旧版本的序列化对象不存在该属性,所以需要添加默认值进行处理

try

{

value2 = info.GetInt32("value2");

}

catch (SerializationException e)

{

// Found version 1.

value2 = DEFAULT_VALUE;

}

}

[SecurityPermissionAttribute(SecurityAction.Demand,

SerializationFormatter = true)]

void ISerializable.GetObjectData(

SerializationInfo inf,

StreamingContext cxt)

{

inf.AddValue("label", label);

inf.AddValue("otherThing", otherThing);

inf.AddValue("value2", value2);

WriteObjectData(inf, cxt); //这里将在父类对象序列化后,用子类的重载的WriteObjectData方法进行子类的成员变量的序列化,是否想起了Effective C# 学习笔记(十七)标准析构模式的实现中提到的析构方法实现?

}

// Overridden in derived classes to write

// derived class data:

protected virtual void WriteObjectData(

SerializationInfo inf,

StreamingContext cxt)

{

// Should be an abstract method,

// if MyType should be an abstract class.

}

}

//子类应提供其自己的序列化构造器,并重载WriteObjectData方法public class DerivedType : MyType

{

private int derivedVal;

private DerivedType(SerializationInfo info,

StreamingContext cntxt) :

base(info, cntxt)//继承父类的序列化构造器

{

//子类自己的成员变量反序列化

derivedVal = info.GetInt32("_DerivedVal");

}

protected override void WriteObjectData(

SerializationInfo inf,

StreamingContext cxt)

{

//序列化子类自己的成员变量

inf.AddValue("_DerivedVal", derivedVal);

}

}

注意自定义属性的序列化实现

最后,还要注意自动属性,因为.NET对于自动属性的实现是通过编译器产生的backing field来进行存储的。而该backing field 的变量名称的是一个非法的C# token(但是个合法的CLR symbol)。你无法去通过自定义序列化构造器或GetObjectData方法来序列化或反序列化这些变量。