DataObjectBase






4.66/5 (20投票s)
DataObjectBase ->
目录
引言
每个开发人员都面临的一个问题是数据持久化。大多数编写的软件都需要能够将对象保存到磁盘,或将其(二进制或 XML)序列化以用于 .NET 远程处理或 Web 服务。 .NET Framework 支持许多接口来实现此目的
但是,当数据对象的属性更改时,您也希望收到通知,现在您正在编写这类软件,并决定同时实现数据验证。需要以下 .NET 接口
发生的情况是,您需要编写大量(冗余)代码来支持所有数据类的所有这些接口。那时您还没有考虑过二进制序列化对象的版本控制,这些对象确实很难处理。此外,在(反)序列化对象时,您会发现自己编写了大量实际上执行相同操作且难以维护的自定义(重复)代码。
如何声明类
该类需要特殊声明。声明看起来代码量很大,但包中包含一个代码片段“dataobject
”,可用于轻松创建新对象。
/// <summary>
/// MyFirstObject Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class MyFirstObject : DataObjectBase
{
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
public MyFirstObject()
: base() { }
/// <summary>
/// Initializes a new object based on .
/// </summary>
/// that contains the information.
/// .
public MyFirstObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
如何声明属性
没有属性的数据类就像没有轮子的自行车。您可以坐在上面,但永远无法骑行。因此,声明属性非常重要。但是,由于类使用特殊的(反)序列化属性,因此需要特殊声明。
要声明属性,请使用包含的代码片段“propdata
”。
/// <summary>
/// Gets or sets the value of MyFirstProperty.
/// </summary>
public string MyFirstProperty
{
get { return GetValue(MyFirstPropertyProperty); }
set { SetValue(MyFirstPropertyProperty, value); }
}
/// <summary>
/// Register the property so it is known in the class.
/// </summary>
public readonly PropertyData MyFirstPropertyProperty =
RegisterProperty("MyFirstProperty", typeof(string), string.Empty);
如您所见,为属性创建了一个特殊包装器,它使用基类的 GetValue 和 SetValue 方法。属性定义中最重要部分是对 RegisterProperty 的调用。第一个参数是属性的名称,第二个参数是属性的类型,第三个参数是默认值,以防属性尚不可用或无法反序列化。
背景
DataObjectBase
类是一个泛型基类,可用于所有数据类。
- 完全可序列化
现在将对象轻松存储在磁盘上或将其序列化为内存(二进制或 XML)非常容易。数据对象开箱即支持此功能,并自动处理(反)序列化。 - 支持属性更改通知
该类支持 INotifyPropertyChanged 通知,因此该类可以轻松地用于 WPF 和 MVC 应用程序,以便向用户显示错误消息。 - 向后兼容性
以二进制方式序列化对象时,很难维护正确的版本。当您向二进制类添加新属性或更改命名空间时,将无法再加载对象。数据对象基类负责解决此问题并支持向后兼容性。 - 错误检查
该类实现了 IDataErrorInfo,因此可以验证数据对象并检查错误。这样,就不需要在数据类外部编写自定义验证代码。 -
备份和恢复
该类实现了 IEditableObject 接口,这使得创建对象状态成为可能。然后可以编辑所有属性,最后可以应用或取消更改。
为了支持所有这些开箱即用的功能,该类实现了以下接口
接口 | 原因 |
ISerializable | 要求使对象可序列化。 |
IXmlSerializable | 要求使用自定义 XML 序列化器使对象可序列化。 |
INotifyPropertyChanging | 要求通知其他类对象内部即将发生的属性更改。 |
INotifyPropertyChanged | 要求通知其他类对象内部发生的属性更改。 |
IDataErrorInfo | 要求通知其他类对象中的数据错误。在 WPF 中显示错误非常有用,可以自动在用户界面中显示错误。 |
IDataWarningInfo | 自定义接口,类似于 IDataErrorInfo,以支持数据对象中的警告。 |
IClonable | 要求支持数据对象的克隆。 |
IComparer |
要求将对象与其它对象进行比较。 |
IEditableObject | 要求在数据对象中创建状态。因此,在用户开始编辑对象之前,可以创建一个状态。然后,可以确认或取消更改。 |
Using the Code
下面列出的所有示例都作为代码包含在本文章附带的可下载包中。大多数示例都有实际应用示例,可以启动以显示其确实有效。 :)
示例 01 – 简单对象
此示例演示如何创建可用作“普通”CLR 对象的类。
/// <summary>
/// SimpleObject Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class SimpleObject : DataObjectBase
{
#region Variables
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
public SimpleObject()
: base() { }
/// <summary>
/// Initializes a new object based on .
/// </summary>
/// that contains the information.
/// .
public SimpleObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endregion
#region Properties
/// </summary>
/// Gets or sets the simple property.
/// </summary>
public string SimpleProperty
{
get { return GetValue(SimplePropertyProperty); }
set { SetValue(SimplePropertyProperty, value); }
}
/// <summary>
/// Register the property so it is known in the class.
/// </summary>
public readonly PropertyData SimplePropertyProperty =
RegisterProperty("SimpleProperty", typeof(string), string.Empty);
#endregion
#region Methods
#endregion
}
示例 02 – 错误验证
此示例演示如何在对象中实现验证。验证将阻止存储无效对象(无效对象无法序列化)。此示例显示了一个使用 WPF 的对象,该对象可以实时向最终用户显示对象中的当前错误。
对象具有 2 种验证结果。可能存在阻止对象序列化的错误,也可能存在仅为警告的警告,因此对象仍可序列化。
要启用验证,您必须至少重写以下方法之一
/// <summary>
/// Validates the fields.
/// </summary>
protected override void ValidateFields()
{
// Validate for warnings
if (string.IsNullOrEmpty(Recommended)) SetFieldWarning(RecommendedProperty,
"The recommended property is missing!");
// Validate for errors
if (string.IsNullOrEmpty(Required)) SetFieldError
(RequiredProperty, "The required property is missing!");
}
/// <summary>
/// Validates the business rules.
/// </summary>
protected override void ValidateBusinessRules()
{
// Check for fields
if (string.IsNullOrEmpty(Recommended) && string.IsNullOrEmpty(Required))
{
SetBusinessRuleError("A combination of field data is not valid,
make sure to enter at least one field!");
}
}
WPF 中的验证将产生以下输出

示例 03 – 序列化
此示例演示如何使用 Save 和 Load 方法将对象序列化/反序列化到/从磁盘。对象本身也可以直接序列化/反序列化到内存,但这在示例中未显示。
要保存对象,请调用对象本身的 Save 方法。
myObject.Save("MyFile.dob", SerializationMode.Binary);
要加载对象,请调用 static
Load 方法来加载类。
SerializableObject myObject = SerializableObject.Load
("MyFile.dob", SerializationMode.Binary);
示例 04 – 嵌套对象
该类完全能够嵌套。这意味着您可以使用基于 DataObjectBase
的对象,其中可以包含也基于 DataObjectBase
的其他类。这使得创建父/子对象成为可能,而整个树都是完全可序列化的。这里没有特别有趣的内容,如果需要更多关于此主题的信息,请查看示例。
示例 05 – 向后兼容性
此示例演示如何将“旧的”(标准 .NET)数据类(使用自定义二进制序列化)轻松转换为 DataObjectBase
,以便甚至为所有现有类使用 DataObjectBase
。
声明一个新的 DataObjectBase
类(记住“dataobject
”代码片段)。如果新类位于新程序集中,或者具有新名称或命名空间,请使用 RedirectType 属性让 DataObjectBase
知道当它找到旧类型时,它应该将该类型反序列化为新类型。
然后,默认情况下,DataObjectBase
类将尝试反序列化旧对象。如果失败,它将回退到属性声明提供的默认值。但是,也可以重写 GetDataFromSerializationInfo 方法。
/// <summary>
/// Retrieves the actual data from the serialization info.
/// </summary>
/// .
/// <remarks>
/// This method should only be implemented if backwards
/// compatibility should be implemented for
/// a class that did not previously implement the DataObjectBase class.
/// </remarks>
protected override void GetDataFromSerializationInfo(SerializationInfo info)
{
// Check if deserialization succeeded
if (DeserializationSucceeded) return;
// Deserialization did not succeed for any reason, so retrieve the values manually
// Luckily there is a helper class (SerializationHelper)
// that eases the deserialization of "old" style objects
FirstName = SerializationHelper.GetString(info,
"FirstName", FirstNameProperty.GetDefaultValue());
LastName = SerializationHelper.GetString(info,
"LastName", LastNameProperty.GetDefaultValue());
}
示例 06 - 恢复和应用
此示例演示如何为对象创建状态。当对象处于编辑模式时,这非常有用。在显示窗口之前,会创建一个新状态。然后,当用户单击“确定”按钮时,新状态将被应用。当用户单击“取消”按钮时,将通过恢复到窗口打开时创建的状态来清除更改。
要创建状态,请使用以下代码:
myObject.BeginEdit();
要应用自上次创建状态以来对对象所做的所有更改,请使用以下代码:
myObject.EndEdit();
要恢复自上次创建状态以来对对象所做的所有更改,请使用以下代码:
myObject.CancelEdit();
关注点
这一切在后台是如何工作的?好吧,这个想法非常基础和简单,它的实现方式并非因为对所有功能和接口的支持。DataObjectBase
类包含一个已注册属性的字典,以及属性的当前值。然后,可以使用 GetValue 和 SetValue 方法在字典中获取或设置值。
最大的问题(实际上,这是一个挑战,而不是问题)是自动反序列化所有数据,特别是在程序集版本号不同或类型重命名的情况下。在反序列化二进制对象时,.NET Framework 非常非常严格,即使类或程序集发生最轻微的更改,也会完全破坏用户数据的反序列化。
DataObjectClass
通过 2 种方式解决了这个问题。首先,它检查 RedirectType 属性,以查看是否应将任何类型重定向到新类型。然后,它初始化自定义 SerializationBinder
来重定向所有类型。这解决了类型重命名问题,也可以处理类型的版本控制。
但是,仍然存在一些问题。在反序列化时,您永远不知道何时您的对象(及其子对象)已完全反序列化。为了解决这个问题,还实现了 OnDeserialized 属性以及 IDeserializationCallback 接口。仅使用其中一个无法解决问题,但同时使用两者可以解决问题。
历史
2010/05/18 - 2.1.0
- 为
IComparer<T>
接口添加了实现。 - 为
IXmlSerializable
接口添加了实现。 - 为
INotifyPropertyChaning
接口添加了实现。 - 添加了接口(
IDataObjectBase
和IDataObjectBase<T>
)以支持模拟并将非泛型对象传递给方法。 - 添加了
Bytes
和KeyName
属性。 - 添加了
OnCreating
和OnCreated
方法。 AlwaysNotifyOnPropertyChanged
现在在Initialize
方法中设置,因此在反序列化对象时也会将其设置为默认值。- 类现在必须实现空构造函数(XML 序列化需要)。
- 在将集合作为属性写入时,不再将 XML 命名空间写入 XML 文件。
- 将二进制格式化程序的创建移至单个方法。
- 改进了单元测试,以检查使用 XML 进行序列化时默认值是否正确替换。
- 修复了由于
IsInitialized
的静态状态导致派生类的属性未正确注册的 bug。 - 修复了非公共属性无法正确(反)序列化的 bug。
2010/04/29 - 2.0.0
- 第一个稳定版本,包含实际示例。