Catel - 第 n 部分的第 1 部分:数据处理应有的样子






4.64/5 (21投票s)
Catel 不仅仅是另一个扩展方法库,也不仅仅是一个 MVVM 框架,它是基本数据处理、有用控件和 MVVM 框架的组合。
Catel 是一个全新的框架(或企业库,随你喜欢怎么称呼),它包含数据处理、诊断、日志记录、WPF 控件和一个 MVVM 框架。因此,Catel 不仅仅是“另一个”MVVM 框架或一些可以使用的不错的扩展方法。它更像是一个你希望在未来开发的所有(WPF)应用程序中都包含的库。
本文将解释该框架的数据处理。
文章浏览器
- Catel - 第 n 部分的第 1 部分:
数据处理应有的样子 - Catel - 第 n 部分 2:使用 WPF 控件和主题
- Catel - 第 3 部分 (共 n 部分):MVVM 框架
- Catel - 第 n 部分 4:使用 Catel 进行单元测试
- Catel - 第 5 部分 (共 n 部分):在 1 小时内构建一个 Catel WPF 示例应用程序
- Catel - 第 6 部分(
共 n 部分): WP7 的 Bing Maps 应用程序 - Catel - 第 n 部分 7:Catel 2.x 有什么新特性
- Catel - 第 n 部分 8:WP7 Mango 和相机单元测试
目录
1. 介绍
欢迎来到 Catel 的介绍。Catel 是一个全新的框架(或企业库,随你喜欢怎么称呼),它包含数据处理、诊断、日志记录、WPF 控件和一个 MVVM 框架。因此,Catel 不仅仅是“另一个”MVVM 框架或一些可以使用的不错的扩展方法。它更像是一个你希望在未来开发的所有(WPF)应用程序中都包含的库。
该框架是使用 C#(.NET Framework 3.5 SP1)开发的。
将有一系列文章详细解释 Catel 的各种功能。以下是文章概述
- Catel (1/n):数据处理的正确方式
- Catel (2/n):使用 WPF 控件和主题
- Catel (3/n):MVVM 框架
- Catel (4/n):使用 Catel 进行单元测试
- Catel (5/n):在 1 小时内使用 Catel 构建一个 WPF 示例应用程序
重要的是要认识到,Catel 不仅仅是另一个扩展方法库,也不仅仅是一个 MVVM 框架,它是基本数据处理、有用控件和 MVVM 框架的组合。
1.1. 为什么要另一个框架?
你可能会想:为什么要另一个框架,外面已经有成千上万个了。好吧,首先,成千上万个有点多,我们姑且说有数百个。几年前,Catel 的首席开发人员使用序列化将数据从磁盘序列化到磁盘,反之亦然。但他注意到,每次发布后,他都必须处理不同的版本。每次发布后,他都必须处理序列化和向后兼容性。此外,他必须为每个数据对象实现一些非常基本的接口(例如 INotifyPropertyChanged
)。然后,他决定编写一个数据处理基类,该基类可以自行处理不同版本和序列化,并开箱即用地实现 .NET Framework 的最基本接口。该文章以 DataObjectBase 的形式发布在 CodeProject 上。
然后,他正在与另外五位开发人员合作一个 WPF 项目,由于当时应用程序没有使用 MVVM,因此需要一个 MVVM 框架。编写一个 MVVM 框架是不可能的,因为已经有太多其他框架了。但是,在查看了一些开源 MVVM 框架(例如出色的 Cinch 框架,这是我们能找到的最好的一个)之后,似乎没有一个真正可行的选择。创建 ViewModel 太费力了,ViewModel 中仍然包含大量重复的代码,例如在属性定义中。在仔细查看 Cinch 和其他框架的源代码后,首席开发人员认为:如果我们使用之前发布的 DataObjectBase 作为 ViewModel 类的基础,那么应该可以在很短的时间内创建一个框架。
然后,他所从事的项目团队的所有其他开发人员都热情高涨,于是整个团队决定将他们的个人库合并到一个大型企业库中,Catel 就此诞生。
1.2. 为什么要使用这个框架?
在继续阅读之前,了解为什么要使用该框架很重要。以下是 Catel 可能对你感兴趣的几个原因
- Catel 是开源的。这样,你可以根据需要进行任何定制。如果你有新的功能请求,但团队响应不够快,你可以自己实现它。
- Catel 的代码库可在 CodePlex 上获取。这样,你可以选择下载最新的稳定版本,或者通过下载最新的源代码来体验最新功能。
- Catel 使用单元测试来确保新更新不会破坏现有功能。
- Catel 的文档非常完善。每个方法和属性都有注释,注释在单独的参考帮助文件中提供。在 CodePlex 上也有大量文档,未来还将撰写深入的文章。
- Catel 由一群才华横溢的软件开发人员开发,并且正在积极开发中。这是一个巨大的优势,因为知识不仅在于一个人,而在于整个团队。Catel 的开发人员都拥有三年以上 WPF 开发经验,并且在实际应用中使用 Catel 至少一年。
1.3. 框架基础
在撰写本文时,该框架由两个项目组成
- Catel.Core - 这是框架的核心,包含 Catel 最重要的类:
DataObjectBase
。大多数数据处理和 MVVM 框架完全依赖于此类。核心还包含扩展方法,以及诊断、反射、日志记录等功能。 - Catel.Windows - 这是 Catel 的 WPF 部分。它包括扩展方法、WPF 控件,最重要的是,MVVM 框架。
该框架的目标是最大限度地减少枯燥代码的重复编写,并专注于应用程序的实际(功能)开发。通过使用 Catel,应用程序的开发时间可以减少至少 50%,但如果你对 Catel 变得更加熟练,效果可能会更好。例如,使用 Catel 附带的 DataWindow
就可以实现一个带“确定”和“取消”按钮的窗口。DataWindow
还自动包含验证。这样,你可以立即开始专注于窗口的实际行为,而不是“确定”和“取消”按钮和数据验证。
在本系列文章的第 4 部分中,将使用 Catel 的大部分内容编写一个示例应用程序。
1.4. 不久将来会发生什么?
团队目前正在完善核心和 WPF 库的代码。一旦稳定,将发布 1.0 版本(预计在年底之前)。然后,以下几点/想法可能会实现
- 添加 Web (MVC) 库 - 团队的一些成员也经验丰富于开发 MVC 应用程序。在不久的将来,他们的个人库将被记录和测试,然后作为 Catel 的一部分公开发布。
- 添加更多 WPF 控件和主题 - 随着时间的推移,将引入新的控件。还计划添加新的配色方案,以支持 Catel 的多种开箱即用主题。
- 全面支持 Silverlight - 目前,对 Silverlight 的支持不是最高优先级(但是,MVVM 框架应该完全兼容)。1.0 版本发布后,团队将详细研究对 Silverlight 的支持。
1.5. 数据处理
每个开发人员面临的问题之一是数据持久性,这正是第一篇文章的主题。大多数编写的软件都需要能够将对象保存到磁盘,或将其序列化(二进制或 XML)以使用 .NET Remoting 或 Web Services。.NET Framework 支持许多接口来实现这一点
但是,你还希望在数据对象的属性更改时收到通知,现在你正在编写这段软件,你决定也实现数据验证。需要以下 .NET 接口
结果是你需要编写大量的(冗余的)代码来支持所有数据类的所有这些接口。那时你甚至还没有考虑二进制序列化对象中的版本控制,这真的很难处理。此外,当(反)序列化对象时,你会发现自己编写了大量自定义(重复的)代码,这些代码实际上做着相同的事情,而且非常难以维护。
Catel 为下面描述的所有问题提供了解决方案:DataObjectBase
类。
2. 特性
DataObjectBase
类是一个通用基类,可用于所有数据类。
- 完全可序列化 - 现在将对象存储到磁盘或将其序列化到内存(二进制或 XML)变得非常容易。数据对象开箱即用地支持此功能,并自动处理(反)序列化。
- 支持属性更改通知 - 该类支持
INotifyPropertyChanging
和INotifyPropertyChanged
接口,因此该类可以轻松用于 WPF 和 MVC 应用程序以向用户反映更改。 - 向后兼容性 - 将对象序列化为二进制时,很难维护正确的版本。当向二进制类添加新属性或更改命名空间时,对象将无法再加载。数据对象基类处理此问题并支持向后兼容性。
- 验证 - 该类实现了
IDataErrorInfo
接口,因此可以验证数据对象并检查错误。这样,无需在数据类之外编写自定义验证代码。 - 备份和恢复 - 该类实现了
IEditableObject
接口,这使得创建对象状态成为可能。然后可以编辑所有属性,最后,可以应用或取消更改。
3. 背景
如果你对 DataObjectBase
的背景和内部工作原理不感兴趣,可以跳过文章的这一部分。
3.1. DataObjectBase
DataObjectBase
类是 Catel 中最重要的类。该类相当复杂,但本文解释了其基础知识。该类本身只包含该类基础所需的属性。派生类的实际属性存储在内部字典中。这些属性可以通过使用 SetValue
和 GetValue
方法从派生类访问。这些方法在内部访问属性字典,但也会处理属性更改期间应发生的事件,例如 PropertyChanging
事件、PropertyChanged
事件和验证。如果 DataObjectBase
包含支持 INotifyPropertyChanged
接口的属性,它将自动订阅这些对象。这样,如果注册的属性也发生更改,派生对象将收到通知。
属性可以通过使用其中一个 static RegisterProperty
方法注册。该方法返回一个 PropertyData
对象,其中包含有关属性的信息,例如名称、类型和默认值。然后,当类构造时,它使用反射来查找所有 PropertyData
对象。这样,它就知道类上注册了哪些属性,并在 PropertyDataManager
上注册类型(稍后详述)。
开箱即用地支持验证。不使用属性进行验证是经过深思熟虑的选择(许多框架使用属性来验证属性),因为错误和警告消息无法通过卫星程序集进行本地化。此外,大多数框架支持一些基本属性,但当更复杂的业务规则出现时,属性将不足以应对,用户仍然必须实现自定义验证。因此,DataObjectBase
支持两个重要方法
ValidateFields
- 字段规则是针对特定字段的规则。一个例子是人不能有负年龄。ValidateBusinessRules
- 业务规则是跨多个属性的规则。一个例子是,一辆车龄超过 10 年(Age
属性)的红色汽车(Color
属性)不能出售。任何字段本身都不是无效的,但组合是。
通过实现 ICloneable
接口,DataObjectBase
开箱即用地支持对象的克隆。调用 Clone
方法时,会创建一个深拷贝。这意味着该类不只是简单地复制第一级属性,因此仍然持有对子对象的引用。DataObjectBase
序列化对象的当前状态,然后将该状态反序列化到一个新对象中。这样,就创建了一个全新的对象,没有任何对原始对象的引用。
DataObjectBase
类还实现了 IEditableObject
接口。当调用 BeginEdit
时,该类将对象的当前状态序列化到内存中,并将该状态保存在一个 internal
属性中。然后,当用户想要取消属性编辑时,旧值将在保存属性实际值的内部字典中恢复。
最后但同样重要的是,该类还实现了 IDisposable
接口。当对象被释放时,它会清除它所持有的任何引用,以确保不会导致内存泄漏。派生类也有机会清理非托管内存。
3.2. 支持类
DataObjectBase
类使用了几个支持类。这些类不会详细解释,但它们仍然足够重要,值得注意。
PropertyData
-PropertyData
类包含有关属性的信息,只能通过调用protected RegisterProperty
方法来构造。该类用于获取属性的默认值、类型信息和已注册的回调方法。PropertyDataManager
-PropertyDataManager
类负责按类型注册属性。除了实际的类型注册之外,它还负责保存所有属性的 XML 映射。
3.3. SavableDataObjectBase
SavableDataObjectBase
是一个派生自 DataObjectBase
类的类。下面的类图显示了 SavableDataObjectBase
提供的扩展

类图清楚地表明 SavableDataObjectBase
能够将自身保存到磁盘和内存中,并从磁盘和内存中加载。该类以两种模式可序列化
二进制
- 对于二进制序列化,该类依赖于BinaryFormatter
类。默认情况下,如果程序集版本发生更改(即使类型本身没有更改),.NET Framework 的二进制格式化程序也会中断。这是因为在二进制序列化期间,程序集的版本号也会存储。而在反序列化时,找不到类型(因为版本已更改),反序列化将失败。为了解决版本更改问题,其中一个
Load
方法重载接受enableRedirects
参数。当启用重定向时,该类将创建一个自定义的SerializationBinder
,以从类型描述符中剥离版本。然后它尝试加载类型(如果该类位于程序集中,则应该成功)。当一个类型完全移动到另一个程序集时,可以使用RedirectTypeAttribute
。这样,RedirectSerializationBinder
将使用新的程序集和类型名称将序列化数据中发现的旧式类型重定向到最新版本中可用的新类型。Xml
- 对于 XML 序列化,该类实现了IXmlSerializable
接口。该类不依赖默认的XmlSerializer
类的原因是,默认序列化程序包含大量垃圾,例如不需要的命名空间。IXmlSerializable
的实现仍然在内部使用XmlSerializer
类,但确保 XML 输出格式正确。
4. 使用类
首先,非常重要的是要认识到,你不应该厌倦自己编写下面的所有代码。Catel 包含许多代码片段,让你可以在短时间内非常轻松地创建数据对象。
4.1. 创建第一个数据对象
解释
此示例展示了使用 DataObjectBase
类声明数据对象的最简单方法。通过使用代码片段,该类仅需 10 秒即可创建。
代码片段
dataobject
- 声明基于DataObjectBase
类的数据对象
步骤
- 创建一个名为 FirstDataObject.cs 的新类文件。
- 在命名空间内,使用
dataobject
代码片段并填写类名,在本例中为FirstDataObject
。
代码
/// <summary>
/// FirstDataObject Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class FirstDataObject : DataObjectBase<FirstDataObject>
{
#region Variables
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
public FirstDataObject()
{ }
/// <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 FirstDataObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endregion
#region Properties
// TODO: Define your custom properties here using the propdata code snippet
#endregion
#region Methods
/// <summary>
/// Validates the fields.
/// </summary>
protected override void ValidateFields()
{
// TODO: Implement any field validation of this object.
// Simply set any error by using the SetFieldError method
}
/// <summary>
/// Validates the business rules.
/// </summary>
protected override void ValidateBusinessRules()
{
// TODO: Implement any business rules of this object.
// Simply set any error by using the SetBusinessRuleError method
}
#endregion
}
4.2. 声明属性
4.2.1. 简单属性
解释
此示例展示了如何声明最简单的属性。在此示例中,将使用代码片段声明一个带默认值的 string
属性。
代码片段
dataprop
- 在数据对象上声明一个简单属性
步骤
- 打开上一个示例中创建的 FirstDataObject.cs。
- 在
Properties
区域中,使用代码片段dataprop
,并使用以下值
代码片段项 | 值 |
description |
获取或设置简单属性 |
type | 字符串 |
名称 | SimpleProperty |
defaultvalue |
“简单属性” |
代码
/// <summary>
/// Gets or sets the simple property.
/// </summary>
public string SimpleProperty
{
get { return GetValue<string>(SimplePropertyProperty); }
set { SetValue(SimplePropertyProperty, value); }
}
/// <summary>
/// Register the SimpleProperty property so it is known in the class.
/// </summary>
public static readonly PropertyDataSimplePropertyProperty =
RegisterProperty("SimpleProperty", typeof(string), "Simple property");
4.2.2. 带属性更改回调的属性
解释
有时你需要知道属性何时发生了更改。你可以通过重写 OnPropertyChanged
方法并检查特定属性是否已更改来做到这一点,但更简单的方法是注册一个仅在该特定属性更改时才调用的回调。
代码片段
datapropchanged
- 在带有属性更改回调的数据对象上声明一个简单属性
步骤
- 打开之前示例中创建的 FirstDataObject.cs。
- 在 Properties 区域中,使用代码片段
datapropchanged
,并使用以下值
代码片段项 | 值 |
description | 获取或设置回调属性 |
type | 字符串 |
名称 | CallbackProperty |
defaultvalue | “回调属性” |
代码
/// <summary>
/// Gets or sets the callback property.
/// </summary>
public string CallbackProperty
{
get { return GetValue<string>(CallbackPropertyProperty); }
set { SetValue(CallbackPropertyProperty, value); }
}
/// <summary>
/// Register the CallbackProperty property so it is known in the class.
/// </summary>
public static readonly PropertyDataCallbackPropertyProperty =
RegisterProperty("CallbackProperty", typeof(string), "Callback property",
(sender, e) => ((FirstDataObject)sender).OnCallbackPropertyChanged());
/// <summary>
/// Called when the CallbackProperty property has changed.
/// </summary>
private void OnCallbackPropertyChanged()
{
// TODO: Implement logic
}
4.3. 添加验证
解释
本示例演示了如何使用 DataObjectBase
类的集成验证。它创建一个新对象,声明两个不同的属性以显示警告和错误类型,并演示如何进行验证。本示例不包含业务规则验证,但其使用方式完全相同。
字段错误映射到 IDataErrorInfo.Item
属性,业务错误映射到 IDataErrorInfo.Error
属性。
代码片段
dataobject
- 声明基于DataObjectBase
类的数据对象dataprop
- 在数据对象上声明一个简单属性
步骤
- 创建一个名为 ValidatingObject.cs 的新类文件。
- 在命名空间内,使用
dataobject
代码片段并填写类名,在本例中为ValidatingObject
。 - 在
Properties
区域中,使用代码片段dataprop
,并使用以下值代码片段项 值 description 获取或设置字段警告属性 type 字符串 名称 FieldWarning defaultvalue “无效字段值” - 在
Properties
区域中,使用代码片段dataprop
,并使用以下值代码片段项 值 description 获取或设置字段错误属性 type 字符串 名称 FieldError defaultvalue “无效字段值” - 现在所有属性都已声明,是时候验证字段了。将以下代码添加到
ValidateFields
方法的主体中// Check warnings if (FieldWarning == "Invalid field value") { SetFieldWarning(FieldWarningProperty, "Property 'FieldWarning' is probably wrong"); } // Check errors if (FieldError == "Invalid field value") { SetFieldError(FieldErrorProperty, "Property 'FieldError' is wrong"); }
代码
/// <summary>
/// ValidatingObject Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class ValidatingObject : DataObjectBase<ValidatingObject>
{
#region Variables
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
public ValidatingObject()
{ }
/// <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 ValidatingObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endregion
#region Properties
/// <summary>
/// Gets or sets the field warning property.
/// </summary>
public string FieldWarning
{
get { return GetValue<string>(FieldWarningProperty); }
set { SetValue(FieldWarningProperty, value); }
}
/// <summary>
/// Register the FieldWarning property so it is known in the class.
/// </summary>
public static readonly PropertyDataFieldWarningProperty =
RegisterProperty("FieldWarning",
typeof(string), "Invalid field value");
/// <summary>
/// Gets or sets the field error property.
/// </summary>
public string FieldError
{
get { return GetValue<string>(FieldErrorProperty); }
set { SetValue(FieldErrorProperty, value); }
}
/// <summary>
/// Register the FieldError property so it is known in the class.
/// </summary>
public static readonly PropertyDataFieldErrorProperty =
RegisterProperty("FieldError", typeof(string), "Invalid field value");
#endregion
#region Methods
/// <summary>
/// Validates the fields.
/// </summary>
protected override void ValidateFields()
{
// Check warnings
if (FieldWarning == "Invalid field value")
{
SetFieldWarning(FieldWarningProperty,
"Property 'FieldWarning' is probably wrong");
}
// Check errors
if (FieldError == "Invalid field value")
{
SetFieldError(FieldErrorProperty, "Property 'FieldError' is wrong");
}
}
/// <summary>
/// Validates the business rules.
/// </summary>
protected override void ValidateBusinessRules()
{
// TODO: Implement any business rules of this object.
// Simply set any error by using the SetBusinessRuleError method
}
#endregion
}
4.4. 将对象保存到磁盘或内存
解释
开箱即用地保存和加载对象从未如此简单。SavableDataObjectBase
可以自动以多种方式(例如内存、不同模式(二进制和 XML)下的文件)保存/加载对象。本示例表明,使对象可保存非常简单,并且不费吹灰之力!
代码片段
dataobject
- 声明基于DataObjectBase
类的数据对象dataprop
- 在数据对象上声明一个简单属性
步骤
- 创建一个名为 SavableObject.cs 的新类文件。
- 在命名空间内,使用
dataobject
代码片段并填写类名,在本例中为SavableObject
。 - 将基类从
DataObjectBase
更改为SavableDataObjectBase
。 - 在
Properties
区域中,使用代码片段dataprop
,并使用以下值代码片段项 值 description 获取或设置名称 type 字符串 名称 名称 defaultvalue “我的名字” - 你现在可以使用任何
Save
方法保存创建的对象。加载可以通过使用static SavableObject.Load
方法完成。
代码
/// <summary>
/// SavableObject Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class SavableObject : SavableDataObjectBase<SavableObject>
{
#region Variables
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
public SavableObject()
{ }
/// <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 SavableObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endregion
#region Properties
/// <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 PropertyDataNameProperty =
RegisterProperty("Name", typeof(string), "MyName");
#endregion
#region Methods
#endregion
}
4.5. 向后兼容性
解释
这个类看起来不错,但是如果你的软件中已经内置了所有序列化和自定义对象怎么办?没问题,Catel 完全支持向后兼容性,并允许你在对象无法由 DataObjectBase
本身反序列化时添加自定义反序列化。
这样,你可以安全地迁移到使用 Catel,并且在软件的未来版本中不再需要担心序列化问题。
代码片段
dataobject
- 声明基于DataObjectBase
类的数据对象dataprop
- 在数据对象上声明一个简单属性
步骤
- 创建一个名为 BackwardsCompatibleObject.cs 的新类文件。
- 在命名空间内,使用
dataobject
代码片段并填写类名,在本例中为BackwardsCompatibleObject
。 - 将基类从
DataObjectBase
更改为SavableDataObjectBase
。 - 在
Properties
区域中,使用代码片段dataprop
,并使用以下值代码片段项 值 description 获取或设置名称 type 字符串 名称 名称 defaultvalue “未知” - 假设旧版本序列化对象在序列化信息中将
Name
属性序列化为_name
。重写GetDataFromSerializationInfo
方法并添加以下代码。如你所见,如果反序列化未成功,则信息会从旧对象手动检索。这只需执行一次;将来,DataObjectBase
类将知道如何正确反序列化对象。
/// <summary>
/// Retrieves the actual data from the serialization info.
/// </summary>
/// <param name="info"><see cref="SerializationInfo"/>.</param>
/// <remarks>
/// This method is called from the OnDeserialized method, thus all child objects
/// are serialized and available at the time this method is called.
/// Only use this method to support older serialization techniques. When using this class
/// for new objects, all serialization is handled automatically.
/// </remarks>
protected override void GetDataFromSerializationInfo(SerializationInfo info)
{
// Check if deserialization succeeded
if (DeserializationSucceeded) return;
// Handle deserialization by ourselves
Name = SerializationHelper.GetString(info, "_name",
NameProperty.GetDefaultValue<string>());
}
代码
/// <summary>
/// BackwardsCompatibleObject Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class BackwardsCompatibleObject : SavableDataObjectBase<BackwardsCompatibleObject>
{
#region Variables
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
publicBackwardsCompatibleObject()
{ }
/// <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 BackwardsCompatibleObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endregion
#region Properties
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name
{
get { returnGetValue<string>(NameProperty); }
set { SetValue(NameProperty, value); }
}
/// <summary>
/// Register the Name property so it is known in the class.
/// </summary>
public static readonly PropertyDataNameProperty =
RegisterProperty("Name", typeof(string), "Unknown");
#endregion
#region Methods
/// <summary>
/// Retrieves the actual data from the serialization info.
/// </summary>
/// <param name="info"><see
/// cref="SerializationInfo"/>.</param>
/// <remarks>
/// This method is called from the OnDeserialized method, thus all child objects
/// are serialized and available at the time this method is called.
/// Only use this method to support older serialization techniques.
/// When using this class
/// for new objects, all serialization is handled automatically.
/// </remarks>
protectedoverridevoidGetDataFromSerializationInfo(SerializationInfo info)
{
// Check if deserialization succeeded
if (DeserializationSucceeded) return;
// Handle deserialization by ourselves
Name = SerializationHelper.GetString(info, "_name",
NameProperty.GetDefaultValue<string>());
}
#endregion
}
4.6. 支持移动/重命名类型和属性
解释
此示例演示如何使用 RedirectTypeAttribute
。此属性可用于通知 DataObjectBase
某个类型已移动或重命名,并应正确重定向到新类型。这样,你可以安全地移动/重命名对象,并且仍然能够反序列化旧对象。
在此示例中,你应该假设在命名空间 Catel.Articles
中存在一个名为 PreviousTypeName
的旧类。但由于这完全错误,现已决定将该类命名为 RenamedObject
并位于新的命名空间中。
注意:为简化起见,此类别中未声明任何属性,因为这只会增加示例的开销。
代码片段
dataobject
- 声明基于DataObjectBase
类的数据对象dataprop
- 在数据对象上声明一个简单属性
步骤
- 创建一个名为 RenamedObject.cs 的新类文件。
- 在命名空间内,使用
dataobject
代码片段并填写类名,在本例中为RenamedObject
。 - 将基类从
DataObjectBase
更改为SavableDataObjectBase
。 - 在类声明的顶部添加带有正确值的
RedirectType
属性[RedirectType("Catel.Articles", "PreviousTypeName")]
- 在反序列化期间,类型
Catel.Articles.PreviousTypeName
将自动重定向到Catel.Articles._02__Data_handling.Models.RenamedObject
。
代码
/// <summary>
/// RenamedObject Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
[RedirectType("Catel.Articles", "PreviousTypeName")]
public class RenamedObject : DataObjectBase<RenamedObject>
{
#region Variables
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
public RenamedObject()
{ }
/// <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 RenamedObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endregion
}
5. 这些类还能做什么?
由于基类 DataObjectBase
实现了所有最常用的接口,因此可能性(几乎)是无限的。例如,此类别也作为 Catel 的 MVVM 框架附带的 ViewModelBase
类的基类。但是,这些类也可以在 MVC 中使用以添加验证。
6. 历史
- 2010 年 11 月 25 日:添加文章浏览器和简要摘要
- 2010 年 11 月 23 日:删除了免责声明,添加了 CodePlex 链接
- 2010 年 9 月 13 日:初始版本