由于本篇的依赖属性体系是基于测试驱动开发完成的所以我们就先来看一下什么叫测试驱动开发测试驱动开发的基本思想就是在开发功能代码之前先编写测试代码。也就是说在明确要开发某个功能后首先思考如何对这个功能进行测试并完成测试代码的编写然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能直到完全部功能的开发。由于过程很长在写的时候也省略了不少步骤所以有些地方衔接不是那么的流畅对此表示非常的抱歉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中的效果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#BTestDriven.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 200832和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的相关功能。1: class ObjectPoker : DependencyObject2: {3: //注册依赖属性property14: public static readonly DependencyProperty TestProp1 DependencyProperty.Register(property1, typeof(string), typeof(ObjectPoker));5: }6:7: class SubclassPoker : ObjectPoker8: {9: }经过上面的测试用例通过以后自然DependencyProperty.Register的基本功能也就完善了然后我们来测试一下Register两个相同的依赖属性有什么反应由于我们为了实现Register时没有考虑那么多所以测试是先会失败然后在引入键值对的形式来存储DependencyProperty然后每个DependencyProperty都用Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode()来区别唯一所以相同下面的测试用例也将完成。1: [Test]2: [ExpectedException(typeof(ArgumentException))]3: public void TestMultipleRegisters()4: {5: //测试注册相同名的依赖属性6: DependencyProperty.Register(p1, typeof(string), typeof(ObjectPoker));7: DependencyProperty.Register(p1, typeof(string), typeof(ObjectPoker));8: }我们说到依赖属性系统其实依赖属性要依附于DependencyObject才能成为真正的依赖属性系统。所以我们来测试一下AddOwner每一个Owner都有自己的元数据这个时候我们需要完善OverrideMetadata方法然而OverrideMetadata方法需要用到PropertyMetadata类作为参数同时需要调用PropertyMetadata类的DoMerge方法我们可以创建该类然后结合Mock完成该操作。1: [Test]2: [ExpectedException(typeof(ArgumentException))]3: public void TestMultipleAddOwner()4: {5: //测试AddOwner添加相同类型的Owner6: ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker), new PropertyMetadata());7: ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker), new PropertyMetadata());8: }通过上面的测试用例以后其实PropertyMetadata的原型已经具备了然后我们要做的就是测试DependencyProperty的默认元数据和默认元数据的默认值。1: [Test]2: public void TestDefaultMetadata()3: {4: //测试默认元数据5: DependencyProperty p;6: p DependencyProperty.Register(TestDefaultMetadata1, typeof(string), typeof(ObjectPoker));7: Assert.IsNotNull(p.DefaultMetadata);8:9: //测试元数据的默认值10: p DependencyProperty.Register(TestDefaultMetadata2, typeof(string), typeof(ObjectPoker), new PropertyMetadata(hi));11: Assert.IsNotNull(p.DefaultMetadata);12: Assert.AreEqual(hi, p.DefaultMetadata.DefaultValue);13: }我们都知道一个DependencyProperty可以拥有多个Owner每个Owner之间的区别就是用PropertyMetadata那么这里就给该DependencyProperty添加一个Owner然后通过该Owner来获取元数据。1: [Test]2: public void TestAddOwnerNullMetadata()3: {4: //首先注册一个依赖属性然后再AddOwner最后根据新的Owner获取元数据5: DependencyProperty p DependencyProperty.Register(TestAddOwnerNullMetadata, typeof(string), typeof(ObjectPoker));6: p.AddOwner(typeof(SubclassPoker), null);7:8: PropertyMetadata pm p.GetMetadata(typeof(SubclassPoker));9: Assert.IsNotNull(pm);10: }通过上面的测试用例我们牵涉到了OverrideMetadata方法当然上面没有进行实现这个时候我们可以来实现OverrideMetadata这个方法首先注册一个ObjectPoker类型的依赖属性然后通过SubclassPoker来OverrideMetadata。1: //首先注册一个依赖属性然后再OverrideMetadata2: [Test]3: [ExpectedException(typeof(ArgumentNullException))]4: public void TestOverrideMetadataNullMetadata()5: {6: //有Type但PropertyMetadata为null时OverrideMetadata操作7: DependencyProperty p DependencyProperty.Register(TestOverrideMetadataNullMetadata, typeof(string), typeof(ObjectPoker));8: p.OverrideMetadata(typeof(SubclassPoker), null);9: }上面实现了OverrideMetadata的函数但是只是简单实现这里我们可以传入一个null类型的Type作为测试当然测试不会通过然后就修改代码直到测试通过吧1: [Test]2: [ExpectedException(typeof(ArgumentNullException))]3: public void TestOverrideMetadataNullType()4: {5: //当Type为nullOverrideMetadata操作6: DependencyProperty p DependencyProperty.Register(TestOverrideMetadataNullType, typeof(string), typeof(ObjectPoker));7: p.OverrideMetadata(null, new PropertyMetadata());8: }如果仔细分析DependencyProperty的源码你会发现有一个DependencyPropertyKey类这个类到底是干嘛的呢其实这个类的主要作用就是构造函数传入该DependencyProperty然后通过Type来OverrideMetadata这里只是提供了一个简单的封装如果没有这个类其他功能照样正常。1: [Test]2: [ExpectedException(typeof(InvalidOperationException))]3: public void TestReadonlyOverrideMetadata()4: {5: //通过DependencyPropertyKey的方式OverrideMetadata6: DependencyPropertyKey ro_key DependencyProperty.RegisterReadOnly(readonly-prop1,7: typeof(double),8: typeof(ObjectPoker),9: new PropertyMetadata(double.NaN));10: ro_key.DependencyProperty.OverrideMetadata(typeof(SubclassPoker), new PropertyMetadataPoker());11: }最后我们来测试一样通过DependencyPropertyKey类来注册一个ReadOnly的依赖属性然后进行OverrideMetadata基本和上一个测试用例类似。1: [Test]2: public void TestReadonlyOverrideMetadataFromKey()3: {4: //通过DependencyPropertyKey的方式OverrideMetadata5: DependencyPropertyKey ro_key DependencyProperty.RegisterReadOnly(readonly-prop2,6: typeof(double),7: typeof(ObjectPoker),8: new PropertyMetadata(double.NaN));9: ro_key.OverrideMetadata(typeof(SubclassPoker), new PropertyMetadataPoker());10: }通过上面的测试用例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的操作。1:2: using System.Collections.Generic;3: namespace System.Windows4: {5: public sealed class DependencyProperty6: {7: //一个依赖属性可能有多个所有者所以根据每个所有者都有自己的元数据8: private DictionaryType,PropertyMetadata metadataByType new DictionaryType,PropertyMetadata();9:10: //声明一个UnsetValue11: public static readonly object UnsetValue new object ();12:13: //依赖属性私有构造函数作为初始化操作每个依赖属性在注册的时候都会调用并初始化数据14: private DependencyProperty (bool isAttached, string name, Type propertyType, Type ownerType,15: PropertyMetadata defaultMetadata,16: ValidateValueCallback validateValueCallback)17: {18: IsAttached isAttached;19: DefaultMetadata (defaultMetadata null ? new PropertyMetadata() : defaultMetadata);20: Name name;21: OwnerType ownerType;22: PropertyType propertyType;23: ValidateValueCallback validateValueCallback;24: }25:26: internal bool IsAttached { get; set; }27: public bool ReadOnly { get; private set; }28: public PropertyMetadata DefaultMetadata { get; private set; }29: public string Name { get; private set; }30: public Type OwnerType { get; private set; }31: public Type PropertyType { get; private set; }32: public ValidateValueCallback ValidateValueCallback { get; private set; }33:34: //获取依赖属性的编号暂未实现,在上一篇“WPF基础到企业应用系列7——深入剖析依赖属性”有实现原理是在初始化的时候35: public int GlobalIndex {36: get { throw new NotImplementedException (); }37: }38:39: //传入ownerType增加Owner40: public DependencyProperty AddOwner(Type ownerType)41: {42: return AddOwner (ownerType, null);43: }44:45: //增加所有者根据ownerType和typeMetadata46: public DependencyProperty AddOwner(Type ownerType, PropertyMetadata typeMetadata)47: {48: if (typeMetadata null) typeMetadata new PropertyMetadata ();49: OverrideMetadata (ownerType, typeMetadata);50:51: // MS seems to always return the same DependencyProperty52: return this;53: }54:55: //获取元数据依据forType56: public PropertyMetadata GetMetadata(Type forType)57: {58: if (metadataByType.ContainsKey (forType))59: return metadataByType[forType];60: return null;61: }62:63: //获取元数据依据该依赖属性64: public PropertyMetadata GetMetadata(DependencyObject d)65: {66: if (metadataByType.ContainsKey (d.GetType()))67: return metadataByType[d.GetType()];68: return null;69: }70:71: //获取元数据依据dependencyObjectType72: public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType)73: {74: if (metadataByType.ContainsKey (dependencyObjectType.SystemType))75: return metadataByType[dependencyObjectType.SystemType];76: return null;77: }78:79: //验证类型是否有效80: public bool IsValidType(object value)81: {82: return PropertyType.IsInstanceOfType (value);83: }84:85: //验证值是否有效86: public bool IsValidValue(object value)87: {88: if (!IsValidType (value))89: return false;90: if (ValidateValueCallback null)91: return true;92: return ValidateValueCallback (value);93: }94:95: //重写元数据使用PropertyMetadata类的DoMerge方法来操作96: public void OverrideMetadata(Type forType, PropertyMetadata typeMetadata)97: {98: if (forType null)99: throw new ArgumentNullException (forType);100: if (typeMetadata null)101: throw new ArgumentNullException (typeMetadata);102:103: if (ReadOnly)104: throw new InvalidOperationException (String.Format (Cannot override metadata on readonly property {0} without using a DependencyPropertyKey, Name));105:106: typeMetadata.DoMerge (DefaultMetadata, this, forType);107: metadataByType.Add (forType, typeMetadata);108: }109:110: //重写元数据使用PropertyMetadata类的DoMerge方法来操作111: public void OverrideMetadata (Type forType, PropertyMetadata typeMetadata, DependencyPropertyKey key)112: {113: if (forType null)114: throw new ArgumentNullException (forType);115: if (typeMetadata null)116: throw new ArgumentNullException (typeMetadata);117:118: typeMetadata.DoMerge (DefaultMetadata, this, forType);119: metadataByType.Add (forType, typeMetadata);120: }121:122: public override string ToString ()123: {124: return Name;125: }126:127: //得到哈希值区别不同的依赖属性Name、PropertyType、OwnerType的哈希值取异128: public override int GetHashCode ()129: {130: return Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode();131: }132:133: //注册依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type134: public static DependencyProperty Register(string name, Type propertyType, Type ownerType)135: {136: return Register(name, propertyType, ownerType, null, null);137: }138:139: //注册依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type、元数据140: public static DependencyProperty Register(string name, Type propertyType, Type ownerType,141: PropertyMetadata typeMetadata)142: {143: return Register(name, propertyType, ownerType, typeMetadata, null);144: }145:146: //注册依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托147: public static DependencyProperty Register(string name, Type propertyType, Type ownerType,148: PropertyMetadata typeMetadata,149: ValidateValueCallback validateValueCallback)150: {151: if (typeMetadata null)152: typeMetadata new PropertyMetadata();153:154: DependencyProperty dp new DependencyProperty(false, name, propertyType, ownerType,155: typeMetadata, validateValueCallback);156: DependencyObject.register(ownerType, dp);157:158: dp.OverrideMetadata (ownerType, typeMetadata);159:160: return dp;161: }162:163: //注册附加依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type164: public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType)165: {166: return RegisterAttached(name, propertyType, ownerType, null, null);167: }168:169: //注册附加依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type、元数据170: public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType,171: PropertyMetadata defaultMetadata)172: {173: return RegisterAttached(name, propertyType, ownerType, defaultMetadata, null);174: }175:176: //注册附加依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托177: public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType,178: PropertyMetadata defaultMetadata,179: ValidateValueCallback validateValueCallback)180: {181: DependencyProperty dp new DependencyProperty(true, name, propertyType, ownerType,182: defaultMetadata, validateValueCallback);183: DependencyObject.register(ownerType, dp);184: return dp;185: }186:187: //注册只读依赖属性暂未实现188: public static DependencyPropertyKey RegisterAttachedReadOnly(string name, Type propertyType, Type ownerType,189: PropertyMetadata defaultMetadata)190: {191: throw new NotImplementedException(RegisterAttachedReadOnly(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata));192: }193:194: //注册只读依赖属性暂未实现195: public static DependencyPropertyKey RegisterAttachedReadOnly(string name, Type propertyType, Type ownerType,196: PropertyMetadata defaultMetadata,197: ValidateValueCallback validateValueCallback)198: {199: throw new NotImplementedException(RegisterAttachedReadOnly(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback));200: }201:202: //注册只读依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type、元数据203: public static DependencyPropertyKey RegisterReadOnly(string name, Type propertyType, Type ownerType,204: PropertyMetadata typeMetadata)205: {206: return RegisterReadOnly (name, propertyType, ownerType, typeMetadata, null);207: }208:209: //注册只读依赖属性参数依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托210: public static DependencyPropertyKey RegisterReadOnly(string name, Type propertyType, Type ownerType,211: PropertyMetadata typeMetadata,212: ValidateValueCallback validateValueCallback)213: {214: DependencyProperty prop Register (name, propertyType, ownerType, typeMetadata, validateValueCallback);215: prop.ReadOnly true;216: return new DependencyPropertyKey (prop);217: }218:219: }220: }通过前面的步骤DependencyProperty已经完成那么下面我们再来看一下DependencyObject类。八. DependencyObject测试代码在写DependencyObject测试代码之前我们先看一下它到底有哪些成员和方法如下图通过上面的这幅图我们知道它的主要功能包括各种依赖属性的GetValue、SetValue操作核心功能和ClearValue、CoerceValue、GetLocalValueEnumerator、ReadLocalValue等操作。为了测试这些功能我们首先创建几个类第一个类X内部首先注册一个附加依赖属性我们都知道不管是附加依赖属性还是依赖属性都需要使用到GetValue和SetValue操作只是一个封装成了属性而另一个封装成了静态方法而已。第二个类直接继承自我们前面在实现DependencyProperty时创建的DependencyObject原型类。1: class X {2: //注册一个附加依赖属性A3: public static readonly DependencyProperty AProperty DependencyProperty.RegisterAttached(A, typeof(int), typeof(X));4: //获取附加属性A的值5: public static void SetA(DependencyObject obj, int value)6: {7: obj.SetValue(AProperty, value);8: }9: //设置附加属性A的值10: public static int GetA(DependencyObject obj)11: {12: return (int)obj.GetValue(AProperty);13: }14: //注册一个附加依赖属性B15: public static readonly DependencyProperty BProperty DependencyProperty.RegisterAttached(B, typeof(string), typeof(X));16: //设置附加属性B的值17: public static void SetB(DependencyObject obj, string value)18: {19: obj.SetValue(BProperty, value);20: }21: //获取附加属性B的值22: public static string GetB(DependencyObject obj)23: {24: return (string)obj.GetValue(BProperty);25: }26:27: }28:29: class Y : DependencyObject {30: }第三个类则是为了直接测试注册一个依赖属性这个类首先继承自DependencyObject原型类。1: class Z : DependencyObject2: {3: public static readonly DependencyProperty SimpleDPProperty 4: DependencyProperty.Register(SimpleDP, typeof(double), typeof(Z),5: new PropertyMetadata((double)0.0,6: new PropertyChangedCallback(OnValueChanged),7: new CoerceValueCallback(CoerceValue)),8: new ValidateValueCallback(IsValidValue));9:10: public double SimpleDP11: {12: get { return (double)GetValue(SimpleDPProperty); }13: set { SetValue(SimpleDPProperty, value); }14: }15:16: private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)17: {18: Console.WriteLine(当值改变时我们可以做的一些操作具体可以在这里定义 {0}, e.NewValue);19: }20:21: private static object CoerceValue(DependencyObject d, object value)22: {23: Console.WriteLine(对值进行限定强制值 {0}, value);24: return value;25: }26:27: private static bool IsValidValue(object value)28: {29: Console.WriteLine(验证值是否通过如果返回True表示验证通过否则会以异常的形式暴露 {0}, value);30: return true;31: }32:33: }首先我们先写测试GetValue和SetValue操作的测试代码然后不能通过最后完善DependencyObject类的GetValue和SetValue方法直到测试用例通过。1: [Test]2: [Category (NotWorking)]3: public void TestAttachedProperty()4: {5: Y y1 new Y();6: X.SetA(y1, 2);7: Assert.AreEqual(2, X.GetA(y1));8: }由于这里是y1和y2两个对象所以他们的GetValue和SetValue也是设置和取得各自的值。1: [Test]2: [Category (NotWorking)]3: public void Test2AttachedProperties()4: {5: Y y1 new Y();6: Y y2 new Y();7: X.SetA(y1, 2);8: X.SetA(y2, 3);9: Assert.AreEqual(2, X.GetA(y1));10: Assert.AreEqual(3, X.GetA(y2));11: }通过前面的图大家可以看到DependencyObject提供了一个取得本地值枚举器的GetLocalValueEnumerator方法它实现一个IEnumerator来方便访问LocalValue这里我们要实现它所以先写测试代码。1: [Test]2: [Category (NotWorking)]3: public void TestEnumerationOfAttachedProperties()4: {5: int count 0;6: Y y new Y();7: X.SetA(y, 2);8: X.SetB(y, Hi);9:10: //根据DependencyObject得到所有本地值11: LocalValueEnumerator e y.GetLocalValueEnumerator();12: while (e.MoveNext()) {13: count;14: if (e.Current.Property X.AProperty)15: Assert.AreEqual(e.Current.Value, 2);16: else if (e.Current.Property X.BProperty)17: Assert.AreEqual(e.Current.Value, Hi);18: else19: Assert.Fail(Wrong sort of property e.Current.Property);20: }21: //count为222: Assert.AreEqual(2, count);23: }