彩票走势图

WPF基础到企业应用系列8——依赖属性之“风云再起”

转帖|其它|编辑:郝浩|2010-11-04 15:46:03.000|阅读 957 次

概述:在上一篇WPF基础到企业应用系列7——深入剖析依赖属性中,我们首先从依赖属性基本介绍讲起,然后过渡到依赖属性的优先级、附加属性、只读依赖属性、依赖属性元数据、依赖属性回调、验证及强制值、依赖属性监听、代码段(自动生成) 等相关知识,最后我们模拟一个WPF依赖属性的实现,由于上篇是根据微软WPF的BCL源码剖析的,所以这篇我们就研究一下.NET的跨平台版本MONO,看下它是怎么来实现这个依赖属性机制。

# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>

一. 摘要

  首先圣殿骑士很高兴”WPF 基础到企业应用系列” 能得到大家的关注、支持和认可。看到很多朋友留言希望加快速度的问题,我会尽力的,对你们的热情关注也表示由衷的感谢。这段时间更新慢的主要原因是因为忙着用TDD还原MONO的框架,同时也因为一直在研究云计算,所以就拖拖拉拉一直没有发布后面的文章。由于WPF整个系列是自己的一些粗浅心得和微薄经验,所以不会像写书那么面面俱到,如果有不足或者错误之处也请大家见谅。在今年之内圣殿骑士会尽量完成”WPF 基础到企业应用系列”和”云计算之旅系列“,诚然,由于本人才识浅薄,所以热切希望和大家共勉!

  由于依赖属性是WPF和Silverlight的核心概念,微软在C\S和B\S平台上主要精力都放到了WPF和Silverlight技术上,同时Silverlight也是Windows Phone的两大编程模型之一(另外一种是XNA),所以我们花费了大量的时间和篇幅进行论述。在上一篇WPF基础到企业应用系列7——深入剖析依赖属性中,我们首先从依赖属性基本介绍讲起,然后过渡到依赖属性的优先级、附加属性、只读依赖属性、依赖属性元数据、依赖属性回调、验证及强制值、依赖属性监听、代码段(自动生成) 等相关知识,最后我们模拟一个WPF依赖属性的实现,由于上篇是根据微软WPF的BCL源码剖析的,所以这篇我们就研究一下.NET的跨平台版本MONO,看下它是怎么来实现这个依赖属性机制。

二. 本文提纲

· 1.摘要

· 2.本文提纲

· 3.兵马未动、废话先行

· 4.依赖属性续前缘

· 5.引入测试驱动开发

· 6.DependencyProperty测试代码

· 7.DependencyProperty实现代码

· 8.DependencyObject测试代码

· 9.DependencyObject实现代码

· 10.PropertyMetadata测试代码

· 11.PropertyMetadata实现代码

· 12.其他协助类测试代码

· 13.其他协助类的实现代码

· 14.回归并统计覆盖率

· 15.简单验证依赖属性系统

· 16.本文总结

· 17.相关代码下载

· 18.系列进度

三. 兵马未动,废话先行

在讲这篇文章之前,我们先来拉一拉家常,说点题外话,就当进入正餐之前的一些甜点,当然这里主要针对.NET平台而言:

1,浅谈软件技术的发展趋势及定位

  互联网的普及应用催生了很多技术的发展与更新,如果仔细深究,你会发现软件技术的发展趋势将主要体现在以下四个方面:客户端软件开发(其中包括客户端软件、游戏、中间件和嵌入式开发等)、Web 开发(包括传统的Web技术、Web游戏以及一些在线应用)、移动设备软件开发(主要涉及到手机等移动设备)、云计算开发(公有云、私有云、混合云会逐渐界限清晰,云厂商以及云平台也会逐渐整合和成熟起来)。就微软来说,这四个方面主要如下:

客户端软件开发

  目前微软主要有Win32 应用程序、MFC 应用程序、WinForm应用程序和WPF 应用程序作为开发选择,目前这四种技术还会共存,因为不同的需求以及不同的人群都有不同的需要。当然WPF借助于其强大的功能和迅猛的发展速度很快会成为首选,这个是值得肯定的。

Web 开发

  在WEB方面微软主要有ASP.NET、ASP.NET MVC、Silverlight三种技术,ASP.NET技术已经发展了多年,在未来的很长一段时间内还会是主流,同时结合Silverlight作为局部和整体应用效果都还很不错,所以这也是很多企业的首选。ASP.NET MVC在目前来说应用还不是特别广泛,不过用过之后感觉也还不错,只是还需要一段时间的适应过程而已。Silverlight在构建局部应用和整站应用都发挥了不错的优势,在Windows Phone中也表现得不错,所以这个技术将会一直热下去。

移动设备软件开发

  移动设备方面可谓是现在众厂商竞争最激烈的市场之一,也是传统技术和新型技术的主要战场之一。微软现在主推的Windows Phone开发主要包括Silverlight和XNA两种技术,Windows Phone开发逐渐变得和ASP.NET开发一样简单,这也是微软的一个目标。

云计算开发

  云计算现在基本上成了互联网的第一大热门词,不管是软件为主导的企业,还是以硬件为主导的企业,都卷入了这场纷争与革命。微软的云平台——Windows Azure Platform,它是微软完整的云计算平台,目前包含了如下三大部分(Windows Azure:运行在云中的操作系统,对于用户来说是虚拟且透明的,其中提供了Compute(计算),Storage(存储),以及Manage(管理)这三个主要功能及其底层服务,使用起来相当的便捷。SQL Azure:运行于云中的一个关系数据库,和SQL Server 2008类似,但是在功能上还没有那么强大。AppFabric:全名是Windows Azure platform AppFabric,提供了访问控制、服务总线等服务,主要用于把基础应用连接到云中)。

其实把这四个方面总结起来就是传说中的微软“三屏一云”战略,从中也可以看出微软逍遥于天地,纵横于宇内,啸傲于世间,雄霸于大地的枭雄战略!

2,浅谈微软跨平台与MONO

  在谈之前我们先看一下什么是MONO?MONO项目是由Ximian发起、Miguel de lcaza领导、Novell公司主持的项目。它是一个致力于开创.NET在Linux,FreeBSD,Unix,Mac OS X和Solaris等其他平台使用的开源工程。它包含了一个C#语言的编译器,一个CLR的运行时,和一组类库,并逐渐实现了 ADO.NET、ASP.NET、WinForm、Silverlight(可惜没有实现强大的WPF),能够使得开发人员在其他平台用C#开发程序。

值得看好的地方:

1,跨平台:开创.NET在Linux,FreeBSD,Unix,Mac OS X和Solaris等其他平台使用,这是微软没有实现的,但是MONO进行了补充,所以值得看好。

2,开源:不论使用什么技术,大家似乎都希望能够用开源的产品,一方面是考虑到技术的可控性和可维护性;另一方面则是考虑到安全性,当然在另一个角度也是可以学习到其中的一些技术和思想,所以大家对开源总是报以欢迎的态度。

3,不同的方式实现.NET框架:由于微软对技术申请了专利,所以MONO不能盲目的模仿,对很多细节都改用自己的方式进行了实现,所以我们也可以学到很多不一样的实现方式。

4,持续更新:MONO从一开始到现在始终在更新,其中包括bug修复、版本升级、增加新的功能及应用,所以相信它会在不断的更新中更加完善。

不足之处:

1.模仿但要避免专利:由于是模仿微软.NET平台,但因为微软对代码申请了专利,所以MONO只能采用其它实现方式来实现同样的功能,这样一来很多地方就会实现得很累赘,效率也会受损。

2.没有摆脱实验产品的头衔:由于它目前的使用比较低,所以信息反馈和持续改进就做得比较弱,这也是目前功能完善得比较慢的原因之一吧。

3,功能还需要完善:一些主要功能还未实现,如作为Windows平台最基础的COM和COM+功能没有保存,像MSMQ等消息队列,消息传送的功能也没有实现,对ADO.NET、XML等核心功能效率有待提升,对BCL库代码也有很多需要优化的地方,强大的WPF也没有引入。

4.效率和用户体验还有待提升。

与微软之间的关系

  微软与MONO之间的关系也一直处于不冷不热的状态,没有明确的反对,也没有明确的支持,究其原因笔者认为主要有以下两点:

1,微软带来最大收益的产品仍旧是Windows操作系统和Office等软件,微软在其他领域盈利都没有这两大产品来得直接。而.NET作为微软的强大开发平台,是不希望落在其他平台上运行的,这样就会削弱Windows操作系统和Office等软件的市场占有率,所以让.NET跨平台对微软来说是一件舍本求末的事情,这也是微软不主张.NET运行于其他平台的主要原因,你想微软是一个以技术为主导的公司,任何IT市场都会有它的身影,如果想让.NET跨平台,那岂不是一件很轻而易举的事情吗?

2,由于MONO还没有成熟,在很多方面都表现得像一个实验室产品,在根本上没有对微软构成威胁,况且在外界质疑.NET是否能跨平台的时候,还有一个现身的说法,所以微软也不会明确的反对和支持。

总结

  虽然目前来说MONO喜忧参半,但优点始终要大于缺点,毕竟每一个框架或者产品都是慢慢不断改进而完善的,更何况开源必将是未来的一个趋势,所以我们有理由也有信心期待它接下来的发展。

3,谈谈源码研究与TDD

  大家都有一个共识:如果你想研究某个框架或者工具的源码,那先必须熟练使用它,熟练之后自然就有一种研究它的冲动,但是往往这个框架或工具比较庞大,很不容易下手,一个很不错的方法就是使用TDD。我们都知道TDD的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完全部功能的开发,在此过程中我们可以借助一些工具来协助。比如我们现在要研究Nhibernate,那么我们首先要熟练它的一些功能,然后从一个点出发慢慢编写单元测试,然后逐渐完善代码,最后直至完成框架的搭建,这样会给我们带来莫大的驱动力和成就感。除了微软的BCL(Base Class Library)和企业库以外,大家还可以用TDD来试试还原以下的任一开源代码:

Spring.NET()、Castle()、log4net()、

NHibernate()、iBATIS.NET()、Caliburn()、

MVVM Light Toolkit()、Prism()、MONO源码()

四. 依赖属性续前缘

  大家都知道WPF和Silverlight带来了很多新的特性,其中一大亮点是引入了一种新的属性机制——依赖属性。依赖属性基本应用在了WPF的所有需要设置属性的元素。依赖属性根据多个提供对象来决定它的值(可以是动画、父类元素、绑定、样式和模板等),同时这个值也能及时响应变化。所以WPF拥有了依赖属性后,代码写起来就比较得心应手,功能实现上也变得非常容易了。如果没有依赖属性,我们将不得不编写大量的代码。依赖属性在WPF中用得非常广泛,具体在以下几个方面中表现得尤为突出:

UI的强大属性体系

Property value inheritance(值继承)

Metadata(强大的元数据)

属性变化通知,限制、验证

Resources(资源)

Data binding(数据绑定)

Styles、Template(样式、模板和风格)

路由事件、附加事件、附加行为乃至命令

Animations、3D(动画和3D)

WPF Designer Integration(WPF设计、开发集成)

  在上一篇WPF基础到企业应用系列7——深入剖析依赖属性中,我们对依赖属性做了较详细的介绍,那么下面我们就简单回顾一下,其实依赖属性的实现很简单,只要做以下步骤就可以实现:

第一步: 让所在类型继承自 DependencyObject基类,在WPF中,我们仔细观察框架的类图结构,你会发现几乎所有的 WPF 控件都间接继承自DependencyObject类型。
第二步:使用 public static 声明一个 DependencyProperty的变量,该变量才是真正的依赖属性 ,看源码就知道这里其实用了简单的单例模式的原理进行了封装(构造函数私有),只暴露Register方法给外部调用。
第三步:在静态构造函数中完成依赖属性的元数据注册,并获取对象引用,看代码就知道是把刚才声明的依赖属性放入到一个类似于容器的地方,没有讲实现原理之前,请容许我先这么陈述。
第四步:在前面的三步中,我们完成了一个依赖属性的注册,那么我们怎样才能对这个依赖属性进行读写呢?答案就是提供一个依赖属性的实例化包装属性,通过这个属性来实现具体的读写操作。

根据前面的四步操作,我们就可以写出下面的代码:

public class SampleDPClass : DependencyObject
{
//声明一个静态只读的DependencyProperty字段
public static readonly DependencyProperty SampleProperty;


static SampleDPClass()
{
//注册我们定义的依赖属性Sample
SampleProperty = DependencyProperty.Register("Sample", typeof(string), typeof(SampleDPClass),
new PropertyMetadata("Knights Warrior!", OnValueChanged));
}

private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
//当值改变时,我们可以在此做一些逻辑处理
}

//属性包装器,通过它来读取和设置我们刚才注册的依赖属性
public string Sample
{
get { return (string)GetValue(SampleProperty); }
set { SetValue(SampleProperty, value); }
}
}

  通过上面的例子可以看出,我们一般.NET属性是直接对类的一个私有属性进行封装,所以读取值的时候,也就是直接读取这个字段;而依赖属性则是通过调用继承自DependencyObject的GetValue()和SetValue来进行操作,它实际存储在DependencyProperty的一个IDictionary的键-值配对字典中,所以一条记录中的键(Key)就是该属性的HashCode值,而值(Value)则是我们注册的DependencyProperty。 回顾了一些基础知识,那我们下面就开始今天的依赖属性系统TDD之旅。

五. 引入测试驱动开发 1,引入概念

  由于本篇的依赖属性体系是基于测试驱动开发完成的,所以我们就先来看一下什么叫测试驱动开发:测试驱动开发的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完全部功能的开发。由于过程很长,在写的时候也省略了不少步骤,所以有些地方衔接不是那么的流畅,对此表示非常的抱歉!

2,注意事项

根据自身做项目使用TDD的一点微薄经验,总结了以下几个注意事项:

◆ 找准切入点:

  不论是开发一个新的系统还是复原系统,都必须先找准一个或多个切入点,从切入点经历”测试代码-功能代码-测试-重构“来逐渐完善整个系统,往往这个切入点就是功能点,就是这个系统具备哪些功能,然后根据这些功能写出测试用例。

◆ 测试列表:

  大家都知道一个系统或者一个框架都是很庞大的,如果要引入测试驱动开发,首先我们必须要有一个测试列表,在任何阶段想添加功能需求问题时,把相关功能点加到测试列表中,然后继续开发的工作。然后不断的完成对应的测试用例、功能代码、重构。这样可以避免疏漏的同时也能把控当前的进度。

◆ 测试驱动:

  这个比较核心。完成某个功能,某个类,首先编写测试代码,考虑其如何使用、如何测试。然后在对其进行设计、编码。这里也强调先编写对功能代码的判断用的断言语句,然后编写相应的辅助语句。

◆ 良好的代码设计及可测性:

功能代码设计、开发时应该具有较强的可测试性。应该尽量保持良好的设计原则和代码规范,如尽量依赖于接口、尽量高内聚、低耦合等等。

◆ 模块或功能隔离:

  不同代码的测试应该相互隔离。对一块代码的测试只考虑此代码的测试,不要考虑其实现细节,不然就会陷入一团乱麻之中,这个可以通过MOCK来实现,同时在开始的时候也要划分好边界。

◆ 适当引入MOCK:

  在适当情况下引入MOCK来完成单元测试,这种情况尤其是在边际交互比较多的案例当中,对于交互比较多且复杂的多个类关系可以用MOCK暂时模拟,这是一个不错的解决方案。

◆ 由小到大、由偏到全、统筹兼顾:

  一个产品或者一个项目是比较大的,所以我们这里就需要遵循由小到大、由偏到全、统筹兼顾的原则,分解功能和代码。把所有的规模大、复杂性高的工作,分解成小的任务来完成,这样既方便团队协作,同时也减轻了复杂度,使整个开发一下子变得简单了许多。

◆ 保持随时重构的习惯

  很多开发者在经过测试代码-功能代码-测试通过以后就当完成了任务,其实你会发现随着其他功能的引入或者使用过程中发现了很多重复、冗余的代码、再或者先前的代码结构和设计不太合理,这个时候就需要随时的进行重构和单元测试,在一方面可以避免产生风险,另一方面可以使系统更加完善。

◆ 随时进行回归:

  在”测试代码-功能代码-测试-重构“的循环中一定要记住多回归,因为这样可以保证当前的代码是不是会影响到前面的功能,其实只需要看看红绿灯就行。

查看和统计代码覆盖率

  通过前面的步骤之后,我们就要看一下实现的功能是否达到我们的预期目标,除了功能完善之外,还要保证代码的覆盖率,因为它是一个系统稳定与否、可维护性与否的一个重大标志。

3,工具介入

  以后写关于TDD的文章可能比较多,同时也都会用到这个工具,所以我们今天对它也稍带介绍一下,正所谓“工欲善其事,必先利其器”。根据官方文档解释:TestDriven.NET是Visual Studio的一个TDD插件,最近版本是TestDriven.NET-3.0.2749 RTM版。其中一些新特性有:支持MSTest、.NET Reflector 6 Pro、VS 2010、Silverlight 4、NUnit 2.5.3,使用项目所用的.NET框架等。 下载地址:  

  这个工具使用起来比VS自带的单元测试和测试覆盖功能好用,所以从2008年开始基本就用它作为一个必备的工具使用。关于它具体的功能和怎么使用,我们这里不详细介绍,网上也有很多文章,大家可以做一下参考和研究。下图是安装后以插件的形式出现在VS中的效果:

 

A,基本介绍

  TestDriven.NET原来叫做NUnitAddIn,它是个Visual Studio插件,集成了如下测试框架:NUnit、MbUnit、 ZaneBug、MSTest、NCover、NCoverExplorer、Reflector、TypeMock、dotTrace和MSBee,它主要面向使用TDD的开发者,主要特性列举如下:

单键运行方法、类、命名空间、项目和解决方案中的单元测试

能够快速测试实例方法、静态方法或属性

可以直接跳到.NET Reflector中的任何方法、类型、项目或引用中,这个功能提供了相当大的方便

在调试过程中可以查看.NET Reflector中的任何模块或堆栈信息

支持多种单元测试框架,包括NUnit、MbUnit、xUnit和MSTest

测试运行在自己的进程中以消除其他问题和边际效应

可以轻松对任何目标测试进行调试或执行代码覆盖率测试(比微软自带的单元测试和代码覆盖功能要好用多了)

支持所有主流的.NET语言:C#、VB、C++和F#

B,TestDriven.NET 3.0中的新特性:

TestDriven.Net是基于.NET框架的。再由于VS 2010支持使用多个.NET版本,所以支持各个VS版本和工具就没有问题了

完全支持在VS 2008和VS 2010中使用MSTest

完全支持.NET Reflector 6 Pro

支持NUnit 2.5.3

支持和兼容VS 2005、VS 2008、VS 2010几个版本

支持Silverlight 4的测试

C,兼容性

  TestDriven.NET兼容于如下VS版本:Windows XP、Vista、Windows 7、Windows 2000、Windows 2003和Windows 2008(32和64位)上的Visual Studio 2005、2008和2010。官方已经不再对VS 2003支持。

D,版本

企业版:每台机器一个许可认证

专业版:一般的许可形式

个人版:面向学生、开源开发者和试验用户的免费许可(大家可以下载这个版本,个人感觉很好用)

4,关于本篇

  本篇文章没有明确的写作意图,只是最近在深入研究MONO源码时有感而发,当然作者本人也只是起到了一个研究者或者剖析者的角色。首先实现最简单且基本的DependencyProperty.Register功能,然后再实现DependencyObject的GetValue和SetValue,接着实现PropertyMetadata的DefaultValue、PropertyChangedCallback、CoerceValueCallback等功能,然后完善DependencyProperty.Register注册时添加ValidateValueCallback、RegisterAttached、RegisterAttachedReadOnly、RegisterReadOnly、OverrideMetadata、GetMetadata和AddOwner等相关功能。既然有了这些功能,自然就需要完善PropertyMetadata的IsSealed、Merge和OnApply等相关底层操作。当然在中间还需要DependencyObject的ClearValue、CoerceValue、GetLocalValueEnumerator、ReadLocalValue以及其他的Helper类,这里就不一一进行说明。对于边际交互比较多且关联比较大的操作,采用了Mock进行暂时模拟,在开发完了以后再进行了替换。在开发过程中,随时进行单元测试和覆盖率的检查,这样可以方便查看哪些功能还有问题以及整体的进度和质量的监控。

六. DependencyProperty测试代码

在写DependencyProperty测试代码之前,我们先看一下它到底有哪些成员和方法,如下图:

 

  了解了上面DependencyProperty的基本功能,我们首先创建一个继承自DependencyObject的类ObjectPoker,由于DependencyObject还没有被创建,所以我们这里就先创建它,然后在ObjectPoker类里面实现我们的经典语句DependencyProperty.Register,由于Register有很多重载,为了方便TDD,就从最简单的开始(三个参数,不牵涉到元数据类),然后再创建一个ObjectPoker的子类,这是方便后面测试DependencyProperty的相关功能。

class ObjectPoker : DependencyObject
{
//注册依赖属性property1
public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1", typeof(string), typeof(ObjectPoker));
}
class SubclassPoker : ObjectPoker
{
}

  经过上面的测试用例通过以后,自然DependencyProperty.Register的基本功能也就完善了,然后我们来测试一下Register两个相同的依赖属性有什么反应,由于我们为了实现Register时没有考虑那么多,所以测试是先会失败,然后在引入键值对的形式来存储DependencyProperty,然后每个DependencyProperty都用Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode()来区别唯一,所以相同下面的测试用例也将完成。

[Test]
[ExpectedException(typeof(ArgumentException))]
public void TestMultipleRegisters()
{
//测试注册相同名的依赖属性
DependencyProperty.Register("p1", typeof(string), typeof(ObjectPoker));
DependencyProperty.Register("p1", typeof(string), typeof(ObjectPoker));
}

  我们说到依赖属性系统,其实依赖属性要依附于DependencyObject才能成为真正的依赖属性系统。所以我们来测试一下AddOwner,每一个Owner都有自己的元数据,这个时候我们需要完善OverrideMetadata方法,然而OverrideMetadata方法需要用到PropertyMetadata类作为参数,同时需要调用PropertyMetadata类的DoMerge方法,我们可以创建该类,然后结合Mock完成该操作。

 

[Test]
[ExpectedException(typeof(ArgumentException))]
public void TestMultipleAddOwner()
{
//测试AddOwner,添加相同类型的Owner
ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker), new PropertyMetadata());
ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker), new PropertyMetadata());
}

通过上面的测试用例以后,其实PropertyMetadata的原型已经具备了,然后我们要做的就是测试DependencyProperty的默认元数据和默认元数据的默认值。

[Test]
public void TestDefaultMetadata()
{
//测试默认元数据
DependencyProperty p;
p = DependencyProperty.Register("TestDefaultMetadata1", typeof(string), typeof(ObjectPoker));
Assert.IsNotNull(p.DefaultMetadata);

//测试元数据的默认值
p = DependencyProperty.Register("TestDefaultMetadata2", typeof(string), typeof(ObjectPoker), new PropertyMetadata("hi"));
Assert.IsNotNull(p.DefaultMetadata);
Assert.AreEqual("hi", p.DefaultMetadata.DefaultValue);
}

  我们都知道一个DependencyProperty可以拥有多个Owner,每个Owner之间的区别就是用PropertyMetadata,那么这里就给该DependencyProperty添加一个Owner,然后通过该Owner来获取元数据。

[Test]
public void TestAddOwnerNullMetadata()
{
//首先注册一个依赖属性,然后再AddOwner,最后根据新的Owner获取元数据
DependencyProperty p = DependencyProperty.Register("TestAddOwnerNullMetadata", typeof(string), typeof(ObjectPoker));
p.AddOwner(typeof(SubclassPoker), null);

PropertyMetadata pm = p.GetMetadata(typeof(SubclassPoker));
Assert.IsNotNull(pm);
}

  通过上面的测试用例,我们牵涉到了OverrideMetadata方法,当然上面没有进行实现,这个时候我们可以来实现OverrideMetadata这个方法,首先注册一个ObjectPoker类型的依赖属性,然后通过SubclassPoker来OverrideMetadata。

//首先注册一个依赖属性,然后再OverrideMetadata
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void TestOverrideMetadataNullMetadata()
{
//有Type但PropertyMetadata为null时,OverrideMetadata操作
DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullMetadata", typeof(string), typeof(ObjectPoker));
p.OverrideMetadata(typeof(SubclassPoker), null);
}

  上面实现了OverrideMetadata的函数,但是只是简单实现,这里我们可以传入一个null类型的Type作为测试,当然测试不会通过,然后就修改代码直到测试通过吧!

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void TestOverrideMetadataNullType()
{
//当Type为null,OverrideMetadata操作
DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullType", typeof(string), typeof(ObjectPoker));
p.OverrideMetadata(null, new PropertyMetadata());
}

  如果仔细分析DependencyProperty的源码,你会发现有一个DependencyPropertyKey类,这个类到底是干嘛的呢?其实这个类的主要作用就是构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata,这里只是提供了一个简单的封装,如果没有这个类,其他功能照样正常。

[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void TestReadonlyOverrideMetadata()
{
//通过DependencyPropertyKey的方式OverrideMetadata
DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop1",
typeof(double),
typeof(ObjectPoker),
new PropertyMetadata(double.NaN));
ro_key.DependencyProperty.OverrideMetadata(typeof(SubclassPoker), new PropertyMetadataPoker());
}

最后我们来测试一样通过DependencyPropertyKey类来注册一个ReadOnly的依赖属性,然后进行OverrideMetadata,基本和上一个测试用例类似。

[Test]
public void TestReadonlyOverrideMetadataFromKey()
{
//通过DependencyPropertyKey的方式OverrideMetadata
DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop2",
typeof(double),
typeof(ObjectPoker),
new PropertyMetadata(double.NaN));
ro_key.OverrideMetadata(typeof(SubclassPoker), new PropertyMetadataPoker()); }

  通过上面的测试用例,DependencyProperty类已经基本完成,除了该类,其他诸如DependencyObject、PropertyMetadata、DependencyPropertyKey也已经初步完成,所以我们这里先以DependencyProperty作为切入点,那么下面就来看一下刚才创建的DependencyProperty类。

七. DependencyProperty实现代码

具体代码如下,我们就不做过多阐述,不过有几点需要注意:

1,一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据。

2,依赖属性私有构造函数,作为初始化操作,每个依赖属性在注册的时候都会调用并初始化数据

3,为了区别不同的依赖属性,Name、PropertyType、OwnerType的哈希值取异。

4,注册依赖属性有以下几个种类:Register、RegisterAttached、RegisterAttachedReadOnly和RegisterReadOnly,所以要区别对待。

5,由于一个依赖属性可能有多个Owner,根据每个Owner都有自己的元数据,所以要有根据Owner的AddOwner、GetMetadata和OverrideMetadata的操作。

using System.Collections.Generic;
namespace System.Windows
{
public sealed class DependencyProperty
{
//一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据
private Dictionary<Type,PropertyMetadata> metadataByType = new
 

Dictionary<Type,PropertyMetadata>();

//声明一个UnsetValue
public static readonly object UnsetValue = new object ();

//依赖属性私有构造函数,作为初始化操作,每个依赖属性在注册的时候都会调用并初始化数
 


private DependencyProperty (bool isAttached, string name, Type propertyType, Type
 

ownerType,
PropertyMetadata defaultMetadata,
ValidateValueCallback validateValueCallback)

{
IsAttached = isAttached;
DefaultMetadata = (defaultMetadata == null ? new PropertyMetadata() :
 

defaultMetadata);
Name = name;
OwnerType = ownerType;
PropertyType = propertyType;
ValidateValueCallback = validateValueCallback;
}

internal bool IsAttached { get; set; }
public bool ReadOnly { get; private set; }
public PropertyMetadata DefaultMetadata { get; private set; }
public string Name { get; private set; }
public Type OwnerType { get; private set; }
public Type PropertyType { get; private set; }
public ValidateValueCallback ValidateValueCallback { get; private set; }
//获取依赖属性的编号,暂未实现,在上一篇"WPF基础到企业应用系列7——深入剖析依赖
 

属性"有实现,原理是在初始化的时候++
public int GlobalIndex {
get { throw new NotImplementedException (); }
}

//传入ownerType增加Owner
public DependencyProperty AddOwner(Type ownerType)
{
return AddOwner (ownerType, null);
}

//增加所有者,根据ownerType和typeMetadata
public DependencyProperty AddOwner(Type ownerType, PropertyMetadata typeMetadata)
{
if (typeMetadata == null) typeMetadata = new PropertyMetadata ();
 

OverrideMetadata (ownerType, typeMetadata);

// MS seems to always return the same DependencyProperty
return this;
}

//获取元数据,依据forType
public PropertyMetadata GetMetadata(Type forType)
{
if (metadataByType.ContainsKey (forType))
return metadataByType[forType];
return null;
}

//获取元数据,依据该依赖属性
public PropertyMetadata GetMetadata(DependencyObject d)
{
if (metadataByType.ContainsKey (d.GetType()))
return metadataByType[d.GetType()];
return null;
}

//获取元数据,依据dependencyObjectType
public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType)
{
if (metadataByType.ContainsKey (dependencyObjectType.SystemType))
 

return metadataByType[dependencyObjectType.SystemType];
return null;
}

//验证类型是否有效
public bool IsValidType(object value)
{
return PropertyType.IsInstanceOfType (value);
}

//验证值是否有效
public bool IsValidValue(object value)
{
if (!IsValidType (value))
return false;
if (ValidateValueCallback == null)
return true;
return ValidateValueCallback (value);
}

//重写元数据,使用PropertyMetadata类的DoMerge方法来操作
public void OverrideMetadata(Type forType, PropertyMetadata typeMetadata)
{
if (forType == null)
throw new ArgumentNullException ("forType");
if (typeMetadata == null)
throw new ArgumentNullException ("typeMetadata");

if (ReadOnly)
throw new InvalidOperationException (String.Format ("Cannot override metadata on
 

readonly property '{0}' without using a DependencyPropertyKey", Name));

typeMetadata.DoMerge (DefaultMetadata, this, forType);
metadataByType.Add (forType, typeMetadata);
}

//重写元数据,使用PropertyMetadata类的DoMerge方法来操作
public void OverrideMetadata (Type forType, PropertyMetadata typeMetadata,
 

DependencyPropertyKey key)

{
if (forType == null)
throw new ArgumentNullException ("forType");
if (typeMetadata == null)
 throw new ArgumentNullException ("typeMetadata");

typeMetadata.DoMerge (DefaultMetadata, this, forType);
metadataByType.Add (forType, typeMetadata);
}

public override string ToString ()
{
return Name; 125: }

//得到哈希值,区别不同的依赖属性,Name、PropertyType、OwnerType的哈希值取异
public override int GetHashCode ()
{
return Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode
 

();
}

//注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type)
public static DependencyProperty Register(string name, Type propertyType, Type
 

ownerType)
{
return Register(name, propertyType, ownerType, null, null); 137: }

//注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据)
public static DependencyProperty Register(string name, Type propertyType, Type
 

ownerType,
PropertyMetadata typeMetadata)
{
return Register(name, propertyType, ownerType, typeMetadata, null);
}

//注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证
 

回调委托) 147: public static DependencyProperty Register(string name, Type
 

propertyType, Type ownerType,
PropertyMetadata typeMetadata,
ValidateValueCallback validateValueCallback)
{
if (typeMetadata == null)
typeMetadata = new PropertyMetadata();

DependencyProperty dp = new DependencyProperty(false, name, propertyType,
 

ownerType,
typeMetadata, validateValueCallback);
DependencyObject.register(ownerType, dp);

dp.OverrideMetadata (ownerType, typeMetadata);

return dp; 161: }

//注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type)
public static Dtached(string name, Type propertyType, Type ownerType)
{
return RegisterAttached(name, propertyType, ownerType, null, null);
}
//注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据)
public static DependencyProperty RegisterAttached(string name, Type
 

propertyType, Type ownerType,
PropertyMetadata defaultMetadata)
{
return RegisterAttached(name, propertyType, ownerType, defaultMetadata, null);
}

//注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调
 

委托)
public static DependencyProperty RegisterAttached(string name, Type propertyType,
 

Type ownerType,
PropertyMetadata defaultMetadata,
ValidateValueCallback validateValueCallback)
{
DependencyProperty dp = new DependencyProperty(true, name, propertyType,
 

ownerType, defaultMetadata, validateValueCallback);
 

DependencyObject.register(ownerType, dp);
return dp;
}

//注册只读依赖属性,暂未实现
public static DependencyPropertyKey RegisterAttachedReadOnly(string name,
 

Type propertyType, Type ownerType,
PropertyMetadata defaultMetadata)
{
throw new NotImplementedException("RegisterAttachedReadOnly(string name, Type
 

propertyType, Type ownerType, PropertyMetadata defaultMetadata)");
}

//注册只读依赖属性,暂未实现
public static DependencyPropertyKey RegisterAttachedReadOnly(string name,

Type propertyType, Type ownerType,
PropertyMetadata defaultMetadata,
ValidateValueCallback validateValueCallback)
{
throw new NotImplementedException("RegisterAttachedReadOnly(string name, Type

propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback

validateValueCallback)");
}

//注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据)
public static DependencyPropertyKey RegisterReadOnly(string name, Type

propertyType, Type ownerType,
PropertyMetadata typeMetadata)
{
return RegisterReadOnly (name, propertyType, ownerType, typeMetadata,

null);
}
//注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、
 

验证回调委托)
public static DependencyPropertyKey RegisterReadOnly(string name, Type
 

propertyType, Type ownerType,
PropertyMetadata typeMetadata,
ValidateValueCallback validateValueCallback)
{
DependencyProperty prop = Register (name, propertyType, ownerType,
 

typeMetadata, validateValueCallback);
prop.ReadOnly = true;
return new DependencyPropertyKey (prop);
}

}
}

通过前面的步骤,DependencyProperty已经完成,那么下面我们再来看一下DependencyObject类。

八. DependencyObject测试代码

在写DependencyObject测试代码之前,我们先看一下它到底有哪些成员和方法,如下图:

 

  通过上面的这幅图,我们知道它的主要功能包括:各种依赖属性的GetValue、SetValue操作(核心功能)和ClearValue、CoerceValue、GetLocalValueEnumerator、ReadLocalValue等操作。为了测试这些功能,我们首先创建几个类,第一个类X,内部首先注册一个附加依赖属性,我们都知道,不管是附加依赖属性还是依赖属性,都需要使用到GetValue和SetValue操作,只是一个封装成了属性,而另一个封装成了静态方法而已。第二个类直接继承自我们前面在实现DependencyProperty时创建的DependencyObject原型类。

class X {
//注册一个附加依赖属性A
public static readonly DependencyProperty AProperty = DependencyProperty.RegisterAttached("A", typeof(int), typeof(X));
//获取附加属性A的值
public static void SetA(DependencyObject obj, int value)
{
obj.SetValue(AProperty, value);
}
//设置附加属性A的值
public static int GetA(DependencyObject obj)
{
return (int)obj.GetValue(AProperty);
}
//注册一个附加依赖属性B
public static readonly DependencyProperty BProperty = DependencyProperty.RegisterAttached("B", typeof(string), typeof(X));
//设置附加属性B的值
public static void SetB(DependencyObject obj, string value)
{
obj.SetValue(BProperty, value);
}
//获取附加属性B的值
public static string GetB(DependencyObject obj)
{
return (string)obj.GetValue(BProperty);
}

}

class Y : DependencyObject {
}

第三个类则是为了直接测试注册一个依赖属性,这个类首先继承自DependencyObject原型类。

class Z : DependencyObject
{
public static readonly DependencyProperty SimpleDPProperty =
DependencyProperty.Register("SimpleDP", typeof(double), typeof(Z),
new PropertyMetadata((double)0.0,
new PropertyChangedCallback(OnValueChanged),
new CoerceValueCallback(CoerceValue)),
new ValidateValueCallback(IsValidValue));

public double SimpleDP
{
get { return (double)GetValue(SimpleDPProperty); }
set { SetValue(SimpleDPProperty, value); }
}

private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
}

private static object CoerceValue(DependencyObject d, object value)
{
Console.WriteLine("对值进行限定,强制值: {0}", value);
return value;
}

private static bool IsValidValue(object value)
{
Console.WriteLine("验证值是否通过,如果返回True表示验证通过,否则会以异常的形式暴露: {0}", value);
return true;
}

}

首先我们先写测试GetValue和SetValue操作的测试代码,然后不能通过,最后完善DependencyObject类的GetValue和SetValue方法直到测试用例通过。

[Test]
[Category ("NotWorking")]
public void TestAttachedProperty()
{
Y y1 = new Y();
X.SetA(y1, 2);
Assert.AreEqual(2, X.GetA(y1));
}

由于这里是y1和y2两个对象,所以他们的GetValue和SetValue也是设置和取得各自的值。

[Test]
[Category ("NotWorking")]
public void Test2AttachedProperties()
{
Y y1 = new Y();
Y y2 = new Y();
X.SetA(y1, 2);
X.SetA(y2, 3);
Assert.AreEqual(2, X.GetA(y1));
Assert.AreEqual(3, X.GetA(y2));
}

  通过前面的图,大家可以看到DependencyObject提供了一个取得本地值枚举器的GetLocalValueEnumerator方法,它实现一个IEnumerator来方便访问LocalValue,这里我们要实现它,所以先写测试代码。

[Test]
[Category ("NotWorking")]
public void TestEnumerationOfAttachedProperties()
{
int count = 0;
Y y = new Y();
X.SetA(y, 2);
X.SetB(y, "Hi");

//根据DependencyObject得到所有本地值
LocalValueEnumerator e = y.GetLocalValueEnumerator();
while (e.MoveNext()) {
count++;
if (e.Current.Property == X.AProperty)
Assert.AreEqual(e.Current.Value, 2);
else if (e.Current.Property == X.BProperty)
Assert.AreEqual(e.Current.Value, "Hi");
else
Assert.Fail("Wrong sort of property" + e.Current.Property);
}
//count为2
Assert.AreEqual(2, count);
}

还有几个功能,既然Mono也没做研究,我们也就不费那个力气了,接下来我们就看看刚才实现的DependencyObject代码吧!

九. DependencyObject实现代码

通过前面的测试用例,DependencyObject类的基本功能已经完成,不过我们要注意几个要点:
1,依赖属性其实终究要DependencyObject和DependencyProperty成对才能算得上真正的DependencyProperty

2,不管是Register、RegisterAttached、RegisterAttachedReadOnly还是RegisterReadOnly操作,我们都要通过DependencyObject来操作DependencyProperty的值,也就是通过DependencyObject这个外部接口来操作,DependencyProperty只负责注册和内部处理,不负责外部接口。

3,在DependencyObject中提供了几个操作LocalValue的接口的接口,其中包括ReadLocalValue、GetLocalValueEnumerator、CoerceValue和ClearValue等。

4,在注册注册依赖属性时,实质是关联DependencyObject的propertyDeclarations,它是一个Dictionary<Type,Dictionary<string,DependencyProperty>>类型,但是在register代码中并没有完全关联起来,我也比较纳闷,所以这点还希望和大家一起探讨,微软的BCL并没有这么实现。

//using System.Windows.Threading;

namespace System.Windows
{
public class DependencyObject
{
//依赖属性其实终究要DependencyObject和DependencyProperty成对才能算得上真
 

正的DependencyProperty
private static Dictionary<Type,Dictionary<string,DependencyProperty>>
 

propertyDeclarations = new Dictionary<Type,Dictionary<string,DependencyProperty>>();
 

//该依赖属性的键值对,键为DependencyProperty,值为object
private Dictionary<DependencyProperty,object> properties = new
 

Dictionary<DependencyProperty,object>();

//是否已密封,没有实现DependencyObject层次的IsSealed判断
public bool IsSealed {
get { return false; }
}

//获取该DependencyObject的DependencyObjectType
public DependencyObjectType DependencyObjectType {
get { return DependencyObjectType.FromSystemType (GetType()); }
}

//根据该依赖属性名,清除它的值
public void ClearValue(DependencyProperty dp)
{
if (IsSealed)
throw new InvalidOperationException ("Cannot manipulate property values
 

on a sealed DependencyObject");

properties[dp] = null;
}

//根据该依赖属性DependencyPropertyKey,清除它的值
public void ClearValue(DependencyPropertyKey key)
{
ClearValue (key.DependencyProperty);
}

//根据该依赖属性名,强制值
public void CoerceValue (DependencyProperty dp)
{
PropertyMetadata pm = dp.GetMetadata (this);
if (pm.CoerceValueCallback != null)
pm.CoerceValueCallback (this, GetValue (dp));
}

public sealed override bool Equals (object obj)
{
throw new NotImplementedException("Equals");
}

public sealed override int GetHashCode ()
{
throw new NotImplementedException("GetHashCode");
}

//得到本地值的枚举器
public LocalValueEnumerator GetLocalValueEnumerator()
{
return new LocalValueEnumerator(properties);
}

//根据依赖属性名获取值
public object GetValue(DependencyProperty dp)
{
object val = properties[dp];
return val == null ? dp.DefaultMetadata.DefaultValue : val;
}


public void InvalidateProperty(DependencyProperty dp)
{
throw new NotImplementedException("InvalidateProperty(DependencyProperty dp)");
}

//当属性值改变时,触发回调
protected virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
PropertyMetadata pm = e.Property.GetMetadata (this);
if (pm.PropertyChangedCallback != null)
pm.PropertyChangedCallback (this, e);
}

//提供一个外界查看LocalValue的接口
public object ReadLocalValue(DependencyProperty dp)
{
object val = properties[dp];
return val == null ? DependencyProperty.UnsetValue : val;
}

//根据依赖属性名设置其值
public void SetValue(DependencyProperty dp, object value)
{
if (IsSealed)
throw new InvalidOperationException ("Cannot manipulate property values on a
 

sealed DependencyObject");

if (!dp.IsValidType (value))
throw new ArgumentException ("value not of the correct type for this
 

DependencyProperty");

ValidateValueCallback validate = dp.ValidateValueCallback;
if (validate != null && !validate(value))
throw new Exception("Value does not validate");
else
properties[dp] = value;
}

//根据依赖属性DependencyPropertyKey设置其值
public void SetValue(DependencyPropertyKey key, object value)
{
SetValue (key.DependencyProperty, value);
}

protected virtual bool ShouldSerializeProperty (DependencyProperty dp) {
throw new NotImplementedException ();
}

//这里的注册实质是关联propertyDeclarations
internal static void register(Type t, DependencyProperty dp)
{
if (!propertyDeclarations.ContainsKey (t))
propertyDeclarations[t] = new Dictionary<string,DependencyProperty>();
Dictionary<string,DependencyProperty> typeDeclarations =
 

propertyDeclarations[t];
if (!typeDeclarations.ContainsKey(dp.Name))
{
typeDeclarations[dp.Name] = dp;
//这里仍然有一些问题,期待各位共同探讨解决
}
else
throw new ArgumentException("A property named " + dp.Name + " already
 

exists on " + t.Name);
}
}
}

  通过前面对DependencyObject和DependencyProperty的研究之后,我们来看看最重要的一个角色,这也是微软最喜欢用的概念——元数据,如果大家研究过微软BCL的源码,应该都知道,它是贯穿于整个CLR当中的。

十. PropertyMetadata测试代码

前面我们看到一个依赖属性的注册最全的形式是下面这样子的:

public static DependencyProperty Register(string name,
Type propertyType,
Type ownerType,
PropertyMetadata typeMetadata,
ValidateValueCallback validateValueCallback);

  第一个参数是该依赖属性的名字,第二个参数是依赖属性的类型,第三个参数是该依赖属性的所有者的类型,第五个参数就是一个验证值的回调委托,那么最使我们感兴趣的还是这个可爱的 PropertyMetadata ,也就是我们接下来要讲的元数据。 提到WPF属性元数据,大家可能第一想到的是刚才的PropertyMetadata,那么这个类到底是怎样的呢?我们应该怎样使用它呢?首先我们看它的构造函数(我们选参数最多的来讲):

public PropertyMetadata(object defaultValue,
PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback);

  其中的第一个参数是默认值,最后两个分别是PropertyChanged(变化通知)以及Coerce(强制)的两个委托变量,我们在实例化的时候,只需要把这两个委托变量关联到具体的方法上即可。

  事实上,除了PropertyMetadata以外,常见的还有FrameworkPropertyMetadata,UIPropertyMetadata。他们的继承关系是F->U->P。其中以FrameworkPropertyMetadata参数最多,亦最为复杂。

FrameworkPropertyMetadata的构造函数提供了很多重载,我们挑选最为复杂的重载来看它到底有哪些参数以及提供了哪些功能:

public FrameworkPropertyMetadata(object defaultValue,
FrameworkPropertyMetadataOptions flags,
PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback,
bool isAnimationProhibited,
UpdateSourceTrigger defaultUpdateSourceTrigger);

  其中第一个参数是默认值,最后两个参数分别是是否允许动画,以及绑定时更新的策略(在Binding当中相信大家并不陌生),这个不详细解释了。重点看一下里第三、四两个参数,两个 CallBack的委托。结合前面Register的时候提到的ValidateValueCallback共组成三大”金刚“,这三个Callback分别代表Validate(验证),PropertyChanged(变化通知)以及Coerce(强制)。当然,作为 Metadata,FrameworkPropertyMetadata只是储存了该依赖属性的策略信息,WPF属性系统会根据这些信息来提供功能并在适当的时机回调传入的delegate,所以最重要的还是我们定义的这些方法,通过他们传入委托才能起到真正的作用。

具体PropertyMetadata包含哪些成员呢?我们先看微软的PropertyMetadata类

 

  在写其他测试用例之前,我们先来创建两个类,第一个类TestDepObj,内部注册了四个依赖属性,前三个没有元数据操作,也就是没有显示声明并构造元数据类,第四个添加了一个元数据类,这个元数据类包含了默认值、值改变回调委托、强制值回调委托。第二个类TestSubclass继承自TestDepObj。

class TestDepObj : DependencyObject
{
public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1", typeof(string), typeof(TestDepObj));
public static readonly DependencyProperty TestProp2 = DependencyProperty.Register("property2", typeof(string), typeof(TestDepObj));
public static readonly DependencyProperty TestProp3 = DependencyProperty.Register("property3", typeof(string), typeof(TestDepObj));

public static readonly DependencyProperty TestProp4 = DependencyProperty.Register("property4", typeof(string), typeof(TestDepObj), new PropertyMetadata("default", changed, coerce));

static void changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
static object coerce(DependencyObject d, object baseValue) { return baseValue; }
}

class TestSubclass : TestDepObj
{
}

  大家看到我们在创建PropertyMetadata的时候对某些功能并没有实现,这里我们就通过子类来具体实现,MONO的这种做法想沿袭微软PropertyMetadata、FrameworkPropertyMetadata和UIPropertyMetadata的做法,但是个人觉得它实现得并不是太好,很多地方感觉很别扭。

//首先我们自定义一个元数据类,继承自我们刚创建的PropertyMetadata类
public class PropertyMetadataPoker : PropertyMetadata
{

public bool BaseIsSealed
{
get { return base.IsSealed; }
}

public void CallApply()
{
OnApply(TestDepObj.TestProp1, typeof(TestDepObj));
}

public void CallMerge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
Merge(baseMetadata, dp);
}

protected override void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
Console.WriteLine(Environment.StackTrace);
base.Merge(baseMetadata, dp);
}

protected override void OnApply(DependencyProperty dp, Type targetType) {
//
base.OnApply(dp, targetType);
Console.WriteLine("IsSealed in OnApply? {0}", IsSealed);
Console.WriteLine(Environment.StackTrace);
}
}

  下面的测试代码主要看一下元数据的默认值,实例化一个元数据类,然后调用它的DefaultValue、PropertyChangedCallback、CoerceValueCallback,测试他们是否为Null。

[Test]
public void DefaultValues()
{
//首先看看元数据的默认值
PropertyMetadataPoker m = new PropertyMetadataPoker();
Assert.AreEqual(null, m.DefaultValue);
Assert.AreEqual(null, m.PropertyChangedCallback);
Assert.AreEqual(null, m.CoerceValueCallback);
}

  我们在WPF和Silverlight中都有过这样的体会:到底什么时候这个依赖属性不能再修改了,其实这个操作得归功于OnApply什么时候触发,我们也可以调用IsSealed来查看,那么这里我们就先写测试代码。第一段代码直接显示调用CallApply方法进行密封;第二段代码则是通过OverrideMetadata操作后内部调用的CallApply;第三段代码是通过AddOwner操作中调用的CallApply;最后一段代码通过调用DependencyProperty.Register时传入元数据,在其内部调用CallApply。

[Test]
public void IsSealed()
{
//测试元数据是否密封,这个很重要,因为封闭之后就不能修改了,除非用OverrideMetadata或者AddOwner
PropertyMetadataPoker m;

Console.WriteLine(1);
// 直接调用 OnApply 查看元数据是否密封
m = new PropertyMetadataPoker();
Assert.IsFalse(m.BaseIsSealed);
m.CallApply();
Assert.IsFalse(m.BaseIsSealed);

Console.WriteLine(2);
// 直接 OverrideMetadata
m = new PropertyMetadataPoker();
TestDepObj.TestProp1.OverrideMetadata(typeof(TestSubclass), m);
Assert.IsTrue(m.BaseIsSealed);

Console.WriteLine(3);
// 调用 DependencyProperty.AddOwner, 通过这种方式 OverrideMetadata
m = new PropertyMetadataPoker();
TestDepObj.TestProp2.AddOwner(typeof(TestSubclass), m);
Assert.IsTrue(m.BaseIsSealed);
Console.WriteLine(4);
// 最后, 调用DependencyProperty.Register时传入元数据
m = new PropertyMetadataPoker();
DependencyProperty.Register("xxx", typeof(string), typeof(TestDepObj), m); Assert.IsTrue(m.BaseIsSealed);
}

下面这段测试代码是验证AddOwner后的DependencyProperty是否和原来的DependencyProperty是同一个DependencyProperty。

[Test]
public void TestAddOwnerResult()
{
//测试AddOwner后的DependencyProperty是否和原来的DependencyProperty是同一个DependencyProperty
PropertyMetadataPoker m = new PropertyMetadataPoker();
DependencyProperty p = TestDepObj.TestProp3.AddOwner(typeof(TestSubclass), m);

//结果是同一个DependencyProperty
Assert.AreSame(p, TestDepObj.TestProp3);
}

  下面这个测试用例是首先实例化元数据并作为注册依赖属性时的参数传入,大家都知道此时如果想修改元数据,可以通过AddOwner或者OverrideMetadata,如果直接赋值,会抛出错误,因为元数据已经密封。

[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void ModifyAfterSealed1()
{
//首先实例化元数据并注册依赖属性时作为参数传入
PropertyMetadataPoker m = new PropertyMetadataPoker();
DependencyProperty.Register("p1", typeof(string), typeof(TestDepObj), m);
Assert.IsTrue(m.BaseIsSealed);

//由于元数据已密封,所以抛出如下错误信息:Cannot change metadata once it has been applied to a property
m.CoerceValueCallback = null;
}

这个和上面的那个测试用例基本一样,只不过把CoerceValueCallback换成了PropertyChangedCallback

[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void ModifyAfterSealed2()
{
//首先实例化元数据并注册依赖属性时作为参数传入
PropertyMetadataPoker m = new PropertyMetadataPoker();
 DependencyProperty.Register("p2", typeof(string), typeof(TestDepObj), m); Assert.IsTrue(m.BaseIsSealed);

//由于元数据已密封,所以抛出如下错误信息:Cannot change metadata once it has been applied to a property
m.PropertyChangedCallback = null;

下面这个测试用例也和上面的两个测试用例类似,它是修改元数据的DefaultValue

[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void ModifyAfterSealed3()
{
//首先实例化元数据并注册依赖属性时作为参数传入
PropertyMetadataPoker m = new PropertyMetadataPoker();
DependencyProperty.Register("p3", typeof(string), typeof(TestDepObj), m);
Assert.IsTrue(m.BaseIsSealed);

//由于元数据已密封,所以抛出如下错误信息:Cannot change metadata once it has been applied to a property
m.DefaultValue = "hi"; 12: }

  通过前面的测试用例,大家可能都会发现有一个Merge这个方法,它在什么时候调用呢?其实它在OverrideMetadata和AddOwner操作中都会调用,在

DependencyProperty中的Register也会显示调用一次。我们需要注意的是:在元数据密封了以后就会抛出错误。

[Test]
public void TestMerge()
{
//需要注意的是:在元数据密封了以后就会抛出错误
PropertyMetadataPoker m = new PropertyMetadataPoker();
m.CallMerge(TestDepObj.TestProp4.GetMetadata(typeof(TestDepObj)), TestDepObj.TestProp4);
Assert.AreEqual("default", m.DefaultValue);
Assert.IsNotNull(m.CoerceValueCallback);
Assert.IsNotNull(m.PropertyChangedCallback);

m = new PropertyMetadataPoker();
m.DefaultValue = "non-default";
m.CallMerge(TestDepObj.TestProp4.GetMetadata(typeof(TestDepObj)), TestDepObj.TestProp4);
Assert.AreEqual("non-default", m.DefaultValue);
Assert.IsNotNull(m.CoerceValueCallback);
Assert.IsNotNull(m.PropertyChangedCallback);

//我们知道元数据包括DefaultValue、 coerce 和 property changed等
//这里我们就不一一测试了,其他测试结果都是一样的
}

下面的测试用例主要是默认值是不能被设置成Unset的

[Test]
[ExpectedException(typeof(ArgumentException))]
public void TestSetDefaultToUnsetValue()
{
//默认值是不能被设置成Unset的
PropertyMetadata m = new PropertyMetadata();
m.DefaultValue = DependencyProperty.UnsetValue;
}

[Test]
[ExpectedException(typeof(ArgumentException))]
public void TestInitDefaultToUnsetValue()
{
//默认值是不能被设置成Unset的
new PropertyMetadata(DependencyProperty.UnsetValue);
}

通过前面的多个测试用例,其实已经包含了PropertyMetadata的基本功能,那我们接下来就看一下PropertyMetadata的内部设计和实现。

十一. PropertyMetadata实现代码

MONO的PropertyMetadata类要比微软的PropertyMetadata类简单很多,不过我们也需要注意一下几点:

1,元数据类包含哪些成员以及有几个构造函数重载?因为这些直接关系到外部的调用。

2,大家要注意ValidateValueCallback不是PropertyMetadata的成员,所以在PropertyMetadata的构造函数中不要把它作为参数传入。

3,注意OnApply函数,因为调用它之后就不能修改元数据的成员,只有通过OverrideMetadata和AddOwner间接实现,如果大家想知道到底这个元数据有没有被密封,可以调用IsSealed属性来查看,这个功能我们也会经常用到。

4,元数据类中提供了Merge的功能,用来方便合并父类和子类的元数据。
 

namespace System.Windows
{
//依赖属性三大回调委托:PropertyChangedCallback、CoerceValueCallback和
 

ValidateValueCallback
public delegate void PropertyChangedCallback(DependencyObject d,
 

DependencyPropertyChangedEventArgs e);
public delegate object CoerceValueCallback(DependencyObject d, object baseValue);
 


public delegate bool ValidateValueCallback(object value);

public class PropertyMetadata
{
private object defaultValue;
private bool isSealed;
private PropertyChangedCallback propertyChangedCallback;
private CoerceValueCallback coerceValueCallback;

//返回该元数据是否已密封
protected bool IsSealed
{
get { return isSealed; }
}

//获取和设置元数据默认值
public object DefaultValue
{
get { return defaultValue; }
set
{
if (IsSealed)
throw new InvalidOperationException("Cannot change metadata once it has been
 

applied to a property");
if (value == DependencyProperty.UnsetValue)
throw new ArgumentException("Cannot set property metadata's default value to
 

'Unset'");

defaultValue = value;
}
}

//ChangedCallback委托赋值,注意检查元数据是否已经密封
public PropertyChangedCallback PropertyChangedCallback
{
get { return propertyChangedCallback; }
set
{
if (IsSealed)
throw new InvalidOperationException("Cannot change metadata once it
 

has been applied to a property");
propertyChangedCallback = value;
}
}

//CoerceValueCallback委托赋值,注意检查元数据是否已经密封
public CoerceValueCallback CoerceValueCallback
{
get { return coerceValueCallback; }
set
{
if (IsSealed)
throw new InvalidOperationException("Cannot change metadata once
 

it has been applied to a property");
coerceValueCallback = value;
}
}

#region PropertyMetadata构造函数,根据不同参数做初始化操作
public PropertyMetadata()
: this(null, null, null)
{
}

public PropertyMetadata(object defaultValue)
: this(defaultValue, null, null)
{
}

public PropertyMetadata(PropertyChangedCallback propertyChangedCallback)
 

: this(null, propertyChangedCallback, null)
{
}

public PropertyMetadata(object defaultValue, PropertyChangedCallback
 

propertyChangedCallback)
: this(defaultValue, propertyChangedCallback, null)
{
}

public PropertyMetadata(object defaultValue, PropertyChangedCallback
 

propertyChangedCallback, CoerceValueCallback coerceValueCallback)
{
if (defaultValue == DependencyProperty.UnsetValue)
throw new ArgumentException("Cannot initialize property metadata's
 

default value to 'Unset'");

this.defaultValue = defaultValue;
this.propertyChangedCallback = propertyChangedCallback;
this.coerceValueCallback = coerceValueCallback;
}
#endregion

//合并元数据
protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty
 

dp)
{
if (defaultValue == null)
defaultValue = baseMetadata.defaultValue;
if (propertyChangedCallback == null)
propertyChangedCallback = baseMetadata.propertyChangedCallback;
 

if (coerceValueCallback == null)
coerceValueCallback = baseMetadata.coerceValueCallback;
}

protected virtual void OnApply(DependencyProperty dp, Type targetType)
 

{
//留给子类来实现吧!
}

//合并元数据并密封
internal void DoMerge(PropertyMetadata baseMetadata, DependencyProperty dp, Type
 

targetType)
{
Merge(baseMetadata, dp);
OnApply(dp, targetType);
isSealed = true;
}
}
}

在上面几个类就是依赖属性系统的核心类,下面将看到几个Helper类。

十二. 其他协助类测试代码

这里就简单写一下对DependencyObjectTypeTest的测试代码:

using System;
using System.Windows;
using NUnit.Framework;

namespace TDDDependencyTest.System.Windows
{
[TestFixture]
public class DependencyObjectTypeTest
{

[Test]
public void Accessors()
{
DependencyObjectType t = DependencyObjectType.FromSystemType(typeof(TestDepObj));
Assert.AreEqual("TestDepObj", t.Name);
Assert.AreEqual(typeof(TestDepObj), t.SystemType);
Assert.AreEqual(typeof(DependencyObject), t.BaseType.SystemType); 18: }

[Test]
public void IsInstanceOfType()
{
DependencyObjectType t = DependencyObjectType.FromSystemType(typeof(TestDepObj));
DependencyObjectType t2 = DependencyObjectType.FromSystemType(typeof(TestSubclass));
Assert.IsTrue(t.IsInstanceOfType(new TestSubclass()));
Assert.IsTrue(t2.IsSubclassOf(t));
Assert.IsFalse(t.IsSubclassOf(t2));
}

[Test]
public void TestCache()
{
DependencyObjectType t = DependencyObjectType.FromSystemType(typeof(TestDepObj));
DependencyObjectType t2 = DependencyObjectType.FromSystemType(typeof(TestDepObj));
Assert.AreSame(t, t2);
}
}
}

由于它的功能比较简单,所以我们就不做过多介绍,大家想了解更多,可以参看代码。

十三. 其他协助类的实现代码

LocalValueEnumerator:手动实现一个IEnumerator来方便访问LocalValue

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace System.Windows
{
//手动实现一个IEnumerator来方便访问LocalValue
public struct LocalValueEnumerator : IEnumerator
{
private IDictionaryEnumerator propertyEnumerator;
private Dictionary<DependencyProperty, object> properties;

private int count;

internal LocalValueEnumerator(Dictionary<DependencyProperty, object> properties)
{
this.count = properties.Count;
this.properties = properties;
this.propertyEnumerator = properties.GetEnumerator();
}

public int Count
{
get { return count; }
}

//获取当前LocalValue
public LocalValueEntry Current
{
get
{
return new LocalValueEntry((DependencyProperty)propertyEnumerator.Key,
propertyEnumerator.Value);
}
}

object IEnumerator.Current
{
get { return this.Current; }
}
 

public bool MoveNext()
{
return propertyEnumerator.MoveNext();
}

//重置propertyEnumerator
public void Reset()
{
propertyEnumerator.Reset();
}

public static bool operator !=(LocalValueEnumerator obj1, LocalValueEnumerator obj2)
{
throw new NotImplementedException();
}

public static bool operator ==(LocalValueEnumerator obj1, LocalValueEnumerator obj2)
{
throw new NotImplementedException();
}

public override bool Equals(object obj)
{
throw new NotImplementedException();
}

public override int GetHashCode()
{
throw new NotImplementedException();
}
}

//LocalValue实体类
public struct LocalValueEntry
{
private DependencyProperty property;
private object value;

internal LocalValueEntry(DependencyProperty property, object value)
{
this.property = property;
this.value = value;
}

public DependencyProperty Property
{
get { return property; }
}

public object Value
{
get { return value; }
}

public static bool operator !=(LocalValueEntry obj1, LocalValueEntry obj2)
{
throw new NotImplementedException();
}

public static bool operator ==(LocalValueEntry obj1, LocalValueEntry obj2)
{
throw new NotImplementedException();
}

public override bool Equals(object obj)
{
throw new NotImplementedException();
}

public override int GetHashCode()
{
throw new NotImplementedException();
}
}
}
 

DependencyPropertyChangedEventArgs:PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)的参数,它的第一个参数为该DependencyProperty、第二个参数为原来的值、第三个参数为改变了的值。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Windows
{
public class DependencyPropertyChangedEventArgs
{
//第一个参数为该DependencyProperty、第二个参数为原来的值、第三个参数为新
 


public DependencyPropertyChangedEventArgs(DependencyProperty property,
 

object oldValue, object newValue)
{
this.Property = property;
this.OldValue = oldValue;
this.NewValue = newValue;
}

//注意所有的属性只对外界开放只读操作
public object NewValue
{
get;
private set;
}

public object OldValue
{
get;
private set;
}

public DependencyProperty Property
{
get;
private set;
}

public override bool Equals(object obj)
{
if (!(obj is DependencyPropertyChangedEventArgs))
return false;

return Equals((DependencyPropertyChangedEventArgs)obj);
}

public bool Equals(DependencyPropertyChangedEventArgs args)
{
return (Property == args.Property &&
NewValue == args.NewValue &&
OldValue == args.OldValue);
}

public static bool operator !=(DependencyPropertyChangedEventArgs left,
 

DependencyPropertyChangedEventArgs right)
{
throw new NotImplementedException();
}

public static bool operator ==(DependencyPropertyChangedEventArgs left,
 

DependencyPropertyChangedEventArgs right)
{
throw new NotImplementedException();
}

public override int GetHashCode()
{
throw new NotImplementedException();
}

}
}

DependencyPropertyKey:构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata,此类只是起到了封装作用。


namespace System.Windows
{
//构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata
public sealed class DependencyPropertyKey
{
internal DependencyPropertyKey (DependencyProperty dependencyProperty) {
this.dependencyProperty = dependencyProperty;
}

private DependencyProperty dependencyProperty;
public DependencyProperty DependencyProperty {
get { return dependencyProperty; }
}

public void OverrideMetadata(Type forType, PropertyMetadata typeMetadata)
{
dependencyProperty.OverrideMetadata (forType, typeMetadata, this); }
}
}

  DependencyObjectType:用静态Dictionary<Type, DependencyObjectType>来存储DependencyObjectType,主要有FromSystemType、IsInstanceOfType和IsSubclassOf三个功能。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Windows
{
public class DependencyObjectType
{
//键为Type(即OwnerType),值为DependencyObjectType(即ID和systemType)的键值对
private static Dictionary<Type, DependencyObjectType> typeMap = new Dictionary<Type, DependencyObjectType>();
private static int current_id;

private int id;
private Type systemType;

//构造函数私有,在FromSystemType里进行构造,初始化id和systemType
private DependencyObjectType(int id, Type systemType)
{
this.id = id;
this.systemType = systemType;
}

//基类型的DependencyObjectType
public DependencyObjectType BaseType
{
get { return DependencyObjectType.FromSystemType(systemType.BaseType); }
}

public int Id
{
get { return id; }
}

public string Name
{
get { return systemType.Name; }
}

public Type SystemType
{
get { return systemType; }
}

//用静态Dictionary<Type, DependencyObjectType>来存储DependencyObjectType
public static DependencyObjectType FromSystemType(Type systemType)
{
if (typeMap.ContainsKey(systemType))
return typeMap[systemType];

DependencyObjectType dot;

typeMap[systemType] = dot = new DependencyObjectType(current_id++, systemType);

return dot;
}

//是否是该DependencyObject的子类实例
public bool IsInstanceOfType(DependencyObject d)
{
return systemType.IsInstanceOfType(d);
}

//该DependencyObjectType是否是传入DependencyObjectType的子实例
public bool IsSubclassOf(DependencyObjectType dependencyObjectType)
{
return systemType.IsSubclassOf(dependencyObjectType.SystemType);
}

public override int GetHashCode()
{
throw new NotImplementedException();
}
}
}
76:

十四. 回归并统计覆盖率

  在上面的开发过程中,我们会不断的运行和查看代码通过情况,最后我们也来看一下测试用例的总体通过情况,其实在前面已经运行过很多次了,因为每个功能都要经过”测试代码-功能代码-测试-重构“等步骤。

 

  最后也看一下代码测试覆盖率,代码测试覆盖率对一个系统或者产品来说是一个比较重要的质量指标,可以通过它看出系统的稳定性和可控性。一般在项目的开发中,我们都会以85%~90%的测试代码覆盖率作为达标的参考标准。

 

  由于MONO本身对依赖属性没有那么健全,我们也没有写那么详细的测试代码,中间直接就实现了一些功能,严格地说,所以本文并没有完全遵从正规的测试驱动开发流程。

十五. 简单验证依赖属性系统

其实通过上面的测试用例,基本就用不着再单独测试了,但鉴于覆盖率比较低的问题,所以最后我们还是来测试一下刚才构建的依赖属性系统:

class Program
{
static void Main(string[] args)
{
SimpleDPClass sDPClass = new SimpleDPClass();
sDPClass.SimpleDP = 8;
Console.ReadLine();
}
}

public class SimpleDPClass : DependencyObject
{
public static readonly DependencyProperty SimpleDPProperty =
DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
new PropertyMetadata((double)0.0,

new PropertyChangedCallback(OnValueChanged),
new CoerceValueCallback(CoerceValue)),
new ValidateValueCallback(IsValidValue));

public double SimpleDP
{
get { return (double)GetValue(SimpleDPProperty); }
set { SetValue(SimpleDPProperty, value); }
}

private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
}

private static object CoerceValue(DependencyObject d, object value)
{
Console.WriteLine("对值进行限定,强制值: {0}", value);
return value;
}

private static bool IsValidValue(object value)
{
Console.WriteLine("验证值是否通过,如果返回True表示验证通过,否则会以异常的形式暴露: {0}", value);
return true;
}

}

测试结果:

 

到处为止,我们这篇文章也宣告结束。

十六. 本文总结

  本篇承接上一篇的写作风格,对上篇模拟一个WPF依赖属性的实现重现演绎了一遍,上篇是根据微软WPF的BCL源码剖析的,所以这篇我们就详细的研究一下.NET的跨平台版本MONO关于依赖属性系统的实现。在这篇文章中,我只是起到了剖析源码的作用,就像研究微软的BCL一样,不过MONO的代码远没有微软的BCL那么庞大,所以研究和复原起来不是很吃力。如果大家还想继续深入,可以去下载相关源码,也希望大家和我一起交流探讨。

十七. 相关代码下载

  在文章的最后,和往常一样,我们提供代码的下载,再次温馨提示:这几篇文章最重要的就是下载代码来细细研究,代码里面也添加了比较详细的注释,如果大家有什么问题,也可以直接和我联系,如果有不正确的地方也希望多多海涵并能给我及时反馈,我将感激不尽!

 
上图就是整个代码包的结构图


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@pclwef.cn

文章转载自:博客转载自圣殿骑士

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP