1,概要
文档的这个部分都是关于Catel中数据处理方法的,一些部分是基于CodeProject上的文章的,但这个文档有更多的更新内容。
一件很重要的事情是许多开发者花费很多时间来处理对象的序列化,序列化是一个专业领域的知识,只有技术比较高深的人员才能很好地掌握对象的序列化处理(要考虑程序集的改变,类的改变(属性的增加和移除),大部分开发者认为序列化只是执行如下的操作代码创建一个BinaryFormattter对象:
var serializer = new BinaryFormatter(); var myObject = (MyObject)serializer.Deserialize(stream);
大部分的开发者并不知道在如下情况下,序列化会出现问题
1,你更改了你的程序集版本
2,你增加或减少了一个属性字段
3,你增加或减少了一个事件
甚至如果你知道,它也需要很多的知识点来解决这个问题,我也是在我足够能够把握这个知识点后才来编写序列化的方法,这个方法是ModelBase类,这个能作为数据对象的基类使用,可以存储到内存也可以序列化到磁盘(或者stream,或者XML,等等…)
2,ObservableObject对象
ObservableObject对象是一个非常轻量级的类仅仅实现了INotifyPropertyChanging和INotifyPropertyChanged接口,这个类是简单对象,仅仅需要属性通知,下面是一个例子:
public class Person : ObservableObject { private string _firstName; private string _middleName; private string _lastName; public Person(string firstName, string middleName, string lastName) { FirstName = firstName; MiddleName = middleName; LastName = lastName; } public string FirstName { get { return _firstName; } set { RaisePropertyChanging(() => FirstName); var oldValue = _firstName; _firstName = value; RaisePropertyChanged(() => FirstName, oldValue, value); } } public string MiddleName { get { return _middleName; } set { RaisePropertyChanging(() => MiddleName); var oldValue = _middleName; _middleName = value; RaisePropertyChanged(() => MiddleName, oldValue, value); } } public string LastName { get { return _lastName; } set { RaisePropertyChanging(() => LastName); var oldValue = _lastName; _lastName = value; RaisePropertyChanged(() => LastName, oldValue, value); } } }
3,DispacherObservableObject
DispatchObservableObject是一个类,继承于ObjservableObject类,这个类的唯一不同是,这个对象会将所有的属性更改反应到UI线程上,下面是一个使用DispatcherObservableObject对象的方法,这是一个线程安全的更改观察.
public class Person : DispatcherObservableObject { private string _firstName; private string _middleName; private string _lastName; public Person(string firstName, string middleName, string lastName) { FirstName = firstName; MiddleName = middleName; LastName = lastName; } public string FirstName { get { return _firstName; } set { RaisePropertyChanging(() => FirstName); var oldValue = _firstName; _firstName = value; RaisePropertyChanged(() => FirstName, oldValue, value); } } public string MiddleName { get { return _middleName; } set { RaisePropertyChanging(() => MiddleName); var oldValue = _middleName; _middleName = value; RaisePropertyChanged(() => MiddleName, oldValue, value); } } public string LastName { get { return _lastName; } set { RaisePropertyChanging(() => LastName); var oldValue = _lastName; _lastName = value; RaisePropertyChanged(() => LastName, oldValue, value); } } }
4,ModelBase
ModelBase(以前是DataObjectBase)类,是一个泛型基类,能够被使用到你所有的数据类上.
序列化
可以将对象存储到硬盘或者序列化到内存,或者二进制文件或者XML,数据对象,数据对象支持立即可用,以及动态处理序列化和反序列化。
支持属性变化观察
这个类支持INotifyPropertyChanging和INotifyChanged接口,类能很容易在WPF,Sliverlight和应用程序来给用户反映改变。
- 向后兼容性
- 当序列化你的对象到二进制中,很难去维护正确的版本,当你增加了一个新的属性给二进制类,或者更改了命名空间,对象将不能被再载入,data object 对象注意到这个问题,支持向后兼容性。
- 验证
这个类实现了IDataErrorInfo接口,,因此他可以验证数据对象检查错误,这样,不需要在类外面写验证代码。 - 备份和恢复
这个类实现了IEdiableObject对象接口,这样可以为对象创造一个状态,所有的属性能够被编辑,最终改变可以被提及或者回滚。
-
4.1 使用类
这个类的使用非常简单,只需要创建一个类继承于ModelBase,如下
/// <summary> /// MyObject class which fully supports serialization, /// property changed notifications, backwards compatibility and error checking. /// </summary> #if !SILVERLIGHT [Serializable] #endif public class MyObject : ModelBase<MyObject> { /// <summary> /// Initializes a new object from scratch. /// </summary> public MyObject() { } #if !SILVERLIGHT /// <summary> /// Initializes a new object based on <see cref="SerializationInfo"/>. /// </summary> /// <param name="info"><see cref="SerializationInfo"/> // that contains the information.</param> /// <param name="context"><see cref="StreamingContext"/>.</param> protected MyObject(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif }
正如你在上面代码中所看到的,MyObject对象继承与ModelBase,并且提供了一个空的构造函数,但是也有一个构造函数用于二进制反序列化,上面的代码看起来复杂,但是这个事事用CodeSnppet来创建的,你只需要填写类的名称即可。
4.1.1 定义属性
为类定义属性也是非常简单,这个与依赖项属性类似,这样定义属性的优点是:
1,属性定义自定包括在序列化中,不需要制定复杂数据接口。
2,你能够为属性指定默认值,当类被构造或者没有在反序列化中发现(在属性被加入到一个存在类的情况)
3,ProperyData对象能够用于返回属性值,编译器可以检查错误,
4,你能够直接订阅改变观察,所有的属性自动支持INotifyPropertyChanged.
下面是字符串代码:
/// <summary> /// Gets or sets the name. /// </summary> public string Name { get { return GetValue<string>(NameProperty); } set { SetValue(NameProperty, value); } } /// <summary> /// Register the Name property so it is known in the class. /// </summary> public static readonly PropertyData NameProperty = RegisterProperty("Name", typeof(string), string.Empty);
一个注册的属性如果需要可以被序列化排除,当对象反序列化时,默认值可以在这种情况下使用。
4.1.2 引用类型的默认值
在许多情况下,引用类型的默认值在定义时是需要的,然而,你可能在依赖属性的理智中注意到这个行为,使用默认值的实例会导致异常行为。
下面是一个“常规”属性,用于定义属性集合
Default values for reference types
public static readonly PropertyData NameProperty = RegisterProperty("PersonCollection", typeof(Collection<Person>), new Collection<Person>());
然而,替代创建一个新的集合对象,仅仅一个集合会被创建在使用这个类,一个方案是将null作为默认值,创建这个集合的构造函数,一个更好的解决方案是重写RegisterProperty的回调参数。
public static readonly PropertyData NameProperty = RegisterProperty("PersonCollection", typeof(Collection<Person>), new Collection<Person>());
这样,每次新的值需要的时候,回调函数都会创建默认值,你将有对所有引用类型都用默认值。
4.2 提供的功能
ModelBase提供许多立即可用的功能,
INotifyPropertyChanged
所用使用RegisterProperty方法注册的属性自动的关注属性更改。
IDataErrorInfo
很容易使用SetFieldError和SetBussinessError方法设置字段和业务错误,也可以重写ValidateFields和ValidateBusinessRules方法。
IEditableObject
数据对象能够创建一个内部的备份和存储,使用IEditableObject接口
.
Serialization
如前面提到的很多次的,使用SavableModelBase,你能够简单的保存你的对象到stream(文件或者是steam)
记住这个类是不适合数据库传输的,有更好的办法去处理它(通过Entity Framework以及NHibernate,LLBLGenPro提供的ORM匹配)