在MultiRow 5.0项目中,我们为序列化做了很多工作。实现了一套非常智能的序列化系统。这套系统可以在不书写任何代码的情况下让大多数对象正确的序列化和反序列化。以确保我们的用户可以实现一些需要序列化的对象(例如,一个自定义的Cell)时尽量少的被序列化所限制。
这一套序列化的机制和标准的XML序列化的机制很类似。通过反射访问和设置对象的属性。但是,我们在其中增加了很多面向控件用户的优化。在大多数情况下,你只要确保你的对象能够在IDE的WinForm Designer中正常工作。就可以确保序列化的正确。
但是,当你需要写一个自定义类型,并且希望它能够被MultiRow的Template使用并被序列化正确,你需要参考下面的指导:
1. 默认构造函数
默认情况下,我们需要调用默认构造函数来构造新对象,因此,你的自定义类型需要实现默认构造函数,并且应该是public的。否则,你需要进行一些高级开发。我将会在将来的某篇文章中介绍怎样实现更多的高级控制。
2. 正确的DefaultValueAttribute或ShouldSerialize支持
一般情况下,我们会采用和标准WinForm Designer中生成代码类似的策略决定是否保存一个属性的值。因此,你必须确保DefaultValue和默认构造函数构造的对象匹配。(这条原则看起来是那么的正常,但是,我们实际工作过程中,发现违反这条原则的实现经常发现)
3. MultiRowSerializeIgnoreAttribute和DesignerSerializationVisibilityAttribute 如果你的对象中包含一些属性是通过其他属性计算得出的,保存和读取这些属性是不必要的或者可能引入错误,你可以通过这个Attribute控制是否序列化这个属性。但是,我更推荐你使用DesignerSerializationVisibilityAttribute.Hidden。这个Attribute会同时控制在WinForm Designer下不生成代码。
对于标准WinForm Designer DesignerSerializationVisibilityAttribute.Hidden和DesignerSerializationVisibilityAttribute.Content具有不同的意义。对于MultiRow的Save/Load来说,他们两个是等价的。
我们建议你正确的配置以上两点,一方面减少不必要的序列化以节省磁盘空间,另一方面,对于性能也有一定好处。
4. 避免属性设置次序影响效果
一般情况下,我们使用字母顺序序列化属性和反序列化属性(通过反射向属性上设值)。如果你的对象上的属性之间有依赖关系,最好确保通过字母序依次设置属性的值时不会产生异常(标准WinForm Designer采用了相同的策略)。
这一点,我们也发现经常出现问题。例如:
在我们实现ComboBoxCell时,发现标准ComboBox上的AutoCompleteMode设置为非None值时,如果DropDownStyle为DropDownList,会产生异常。但是,我们在这一点上没有兼容ComboBox的策略。因为,我们的ComboBoxCell的DropDownStyle默认值为DropDownList。如果使用这样的策略,会导致反序列化时按照字母序首先设置AutoCompleteMode抛出异常。从而影响反序列化的进行。
最终,我们采用了在设置AutoCompleteMode时自动将DropDownStyle属性修改为DropDown的策略。以避免这个问题发生。
5. 自定义Collection应该实现IList接口
应为我们的序列化系统是基于public的属性工作的,因此,他不能识别Add/Remove等方法。如果你的Collection实现了IList接口,我们会枚举并且序列化其中的每一项内容。反序列化时,会调用IList.Add方法将对象添加到Collection中。
在v5.0中,我们不会支持泛型的ICollection<T>接口。我们将会在未来的版本支持这个功能。
另外,我们发现有些时候,开发人员会在一个Collection上添加额外的属性,并且希望这些属性是可以序列化的。我们认为这样的设计是不好的。因此没有提供机制支持这样的设计。也许我们会在未来版本提供这样的支持。
6. 对于泛型的支持不好
非常遗憾,MultiRow v5.0中的序列化对泛型的支持不好。如果你的类型中包含属性的类型是一个泛型类型时。序列化会出错。最简单的绕过这个问题的方法是编写一个空的非泛型类型继承自这个泛型类型,并在属性中使用这个新类型。我们也许会在最近的HotFix中修复这个问题。
我们认为,大多数情况下,以上原则都是能够很容易的被支持的。如果你的对象确实因为一些其他原因无法满足以上的原则,你可能就需要使用到更多的高级功能,包括TypeConverter的支持和MultiRowSerializableTypeDescriptor的支持了。关于这些话题,我将会放在下一篇文章中进行介绍。
另外, 一个我们开发中常见的错误是:调用Template.Load方法时无法找到你自定义的类型。我们会在Xml文件中保存每一个类型的全名(包含他所在程序集的全名),因此,你必须确保调用Template.Load时当前AppDomain中包含一个完全匹配的类型可以使用。