依赖属性之“风云再起”

  一. 摘要

  首先圣殿骑士很高兴”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.NETASP.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.NETASP.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.NEThttp://www.springframework.NET/)、Castle(http://www.castleproject.org)、log4NEThttp://logging.apache.org/log4NET/)、

NHibernate(http://www.hibernate.org/343.html)、iBATIS.NEThttp://ibatis.apache.org)、Caliburn(http://caliburn.codeplex.com/)、

MVVM Light Toolkit(http://mvvmlight.codeplex.com/)、Prism(http://compositewpf.codeplex.com/)、MONO源码(www.mono-project.com

  四. 依赖属性续前缘

  大家都知道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方法给外部调用。
第三步:在静态构造函数中完成依赖属性的元数据注册,并获取对象引用,看代码就知道是把刚才声明的依赖属性放入到一个类似于容器的地方,没有讲实现原理之前,请容许我先这么陈述。
第四步:在前面的三步中,我们完成了一个依赖属性的注册,那么我们怎样才能对这个依赖属性进行读写呢?答案就是提供一个依赖属性的实例化包装属性,通过这个属性来实现具体的读写操作。

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

   1: public class SampleDPClass : DependencyObject
   2:  {
   3:      //声明一个静态只读的DependencyProperty字段
   4:      public static readonly DependencyProperty SampleProperty;
   5:  
   6:      static SampleDPClass()
   7:      {
   8:          //注册我们定义的依赖属性Sample
   9:          SampleProperty = DependencyProperty.Register("Sample", typeof(string), typeof(SampleDPClass),
  10:              new PropertyMetadata("Knights Warrior!", OnValueChanged));
  11:      }
  12:  
  13:      private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  14:      {
  15:          //当值改变时,我们可以在此做一些逻辑处理
  16:      }
  17:  
  18:      //属性包装器,通过它来读取和设置我们刚才注册的依赖属性
  19:      public string Sample
  20:      {
  21:          get { return (string)GetValue(SampleProperty); }
  22:          set { SetValue(SampleProperty, value); }
  23:      }
  24:  }

 

  五. 引入测试驱动开发

  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框架等。 下载地址:http://www.testdriven.NET/  

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

2010-9-23 19-42-57

  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测试代码之前,我们先看一下它到底有哪些成员和方法,如下图:

2010-9-26 23-07-52

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

   1: class ObjectPoker : DependencyObject
   2: {
   3:     //注册依赖属性property1
   4:     public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1", typeof(string), typeof(ObjectPoker));
   5: }
   6:  
   7: class SubclassPoker : ObjectPoker
   8: {
   9: }

 

  九. 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并没有这么实现。

   1: using System.Collections.Generic;
   2: //using System.Windows.Threading;
   3:  
   4: namespace System.Windows 
   5: {
   6:     public class DependencyObject
   7:     {
   8:         //依赖属性其实终究要DependencyObject和DependencyProperty成对才能算得上真正的DependencyProperty
   9:         private static Dictionary<Type,Dictionary<string,DependencyProperty>> propertyDeclarations = new Dictionary<Type,Dictionary<string,DependencyProperty>>();
  10:         //该依赖属性的键值对,键为DependencyProperty,值为object
  11:         private Dictionary<DependencyProperty,object> properties = new Dictionary<DependencyProperty,object>();
  12:  
  13:         //是否已密封,没有实现DependencyObject层次的IsSealed判断
  14:         public bool IsSealed {
  15:             get { return false; }
  16:         }
  17:  
  18:         //获取该DependencyObject的DependencyObjectType
  19:         public DependencyObjectType DependencyObjectType { 
  20:             get { return DependencyObjectType.FromSystemType (GetType()); }
  21:         }
  22:  
  23:         //根据该依赖属性名,清除它的值
  24:         public void ClearValue(DependencyProperty dp)
  25:         {
  26:             if (IsSealed)
  27:                 throw new InvalidOperationException ("Cannot manipulate property values on a sealed DependencyObject");
  28:  
  29:             properties[dp] = null;
  30:         }
  31:  
  32:         //根据该依赖属性DependencyPropertyKey,清除它的值
  33:         public void ClearValue(DependencyPropertyKey key)
  34:         {
  35:             ClearValue (key.DependencyProperty);
  36:         }
  37:  
  38:         //根据该依赖属性名,强制值
  39:         public void CoerceValue (DependencyProperty dp)
  40:         {
  41:             PropertyMetadata pm = dp.GetMetadata (this);
  42:             if (pm.CoerceValueCallback != null)
  43:                 pm.CoerceValueCallback (this, GetValue (dp));
  44:         }
  45:  
  46:         public sealed override bool Equals (object obj)
  47:         {
  48:             throw new NotImplementedException("Equals");
  49:         }
  50:  
  51:         public sealed override int GetHashCode ()
  52:         {
  53:             throw new NotImplementedException("GetHashCode");
  54:         }
  55:  
  56:         //得到本地值的枚举器
  57:         public LocalValueEnumerator GetLocalValueEnumerator()
  58:         {
  59:             return new LocalValueEnumerator(properties);
  60:         }
  61:  
  62:         //根据依赖属性名获取值
  63:         public object GetValue(DependencyProperty dp)
  64:         {
  65:             object val = properties[dp];
  66:             return val == null ? dp.DefaultMetadata.DefaultValue : val;
  67:         }
  68:         
  69:  
  70:         public void InvalidateProperty(DependencyProperty dp)
  71:         {
  72:             throw new NotImplementedException("InvalidateProperty(DependencyProperty dp)");
  73:         }
  74:         
  75:         //当属性值改变时,触发回调
  76:         protected virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
  77:         {
  78:             PropertyMetadata pm = e.Property.GetMetadata (this);
  79:             if (pm.PropertyChangedCallback != null)
  80:                 pm.PropertyChangedCallback (this, e);
  81:         }
  82:  
  83:         //提供一个外界查看LocalValue的接口
  84:         public object ReadLocalValue(DependencyProperty dp)
  85:         {
  86:             object val = properties[dp];
  87:             return val == null ? DependencyProperty.UnsetValue : val;
  88:         }
  89:  
  90:         //根据依赖属性名设置其值
  91:         public void SetValue(DependencyProperty dp, object value)
  92:         {
  93:             if (IsSealed)
  94:                 throw new InvalidOperationException ("Cannot manipulate property values on a sealed DependencyObject");
  95:  
  96:             if (!dp.IsValidType (value))
  97:                 throw new ArgumentException ("value not of the correct type for this DependencyProperty");
  98:  
  99:             ValidateValueCallback validate = dp.ValidateValueCallback;
 100:             if (validate != null && !validate(value))
 101:                 throw new Exception("Value does not validate");
 102:             else
 103:                 properties[dp] = value;
 104:         }
 105:  
 106:         //根据依赖属性DependencyPropertyKey设置其值
 107:         public void SetValue(DependencyPropertyKey key, object value)
 108:         {
 109:             SetValue (key.DependencyProperty, value);
 110:         }
 111:  
 112:         protected virtual bool ShouldSerializeProperty (DependencyProperty dp)
 113:         {
 114:             throw new NotImplementedException ();
 115:         }
 116:  
 117:         //这里的注册实质是关联propertyDeclarations
 118:         internal static void register(Type t, DependencyProperty dp)
 119:         {
 120:             if (!propertyDeclarations.ContainsKey (t))
 121:                 propertyDeclarations[t] = new Dictionary<string,DependencyProperty>();
 122:             Dictionary<string,DependencyProperty> typeDeclarations = propertyDeclarations[t];
 123:             if (!typeDeclarations.ContainsKey(dp.Name))
 124:             {
 125:                 typeDeclarations[dp.Name] = dp;
 126:                 //这里仍然有一些问题,期待各位共同探讨解决
 127:             }
 128:             else
 129:                 throw new ArgumentException("A property named " + dp.Name + " already exists on " + t.Name);
 130:         }
 131:     }
 132: }

NET技术依赖属性之“风云再起”,转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。