您的位置:知识库 » .NET技术

依赖属性之“风云再起”

作者: 圣殿骑士  来源: 博客园  发布时间: 2010-10-01 00:09  阅读: 6025 次  推荐: 0   原文链接   [收藏]  
[1] 依赖属性之“风云再起”
[2] 依赖属性之“风云再起”
[3] 依赖属性之“风云再起”

 

  五. 引入测试驱动开发

  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: }

  经过上面的测试用例通过以后,自然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,添加相同类型的Owner
   6:     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: //首先注册一个依赖属性,然后再OverrideMetadata
   2: [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为null,OverrideMetadata操作
   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的方式OverrideMetadata
   6:     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的方式OverrideMetadata
   5:     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.Windows 
   4: {
   5:     public sealed class DependencyProperty
   6:     {
   7:         //一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据
   8:         private Dictionary<Type,PropertyMetadata> metadataByType = new Dictionary<Type,PropertyMetadata>();
   9:  
  10:         //声明一个UnsetValue
  11:         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增加Owner
  40:         public DependencyProperty AddOwner(Type ownerType)
  41:         {
  42:             return AddOwner (ownerType, null);
  43:         }
  44:  
  45:         //增加所有者,根据ownerType和typeMetadata
  46:         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 DependencyProperty
  52:             return this;
  53:         }
  54:  
  55:         //获取元数据,依据forType
  56:         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:         //获取元数据,依据dependencyObjectType
  72:         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、拥有者的Type)
 134:         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、拥有者的Type)
 164:         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测试代码之前,我们先看一下它到底有哪些成员和方法,如下图:

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

   1: class X {
   2:     //注册一个附加依赖属性A
   3:     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:     //注册一个附加依赖属性B
  15:     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 : DependencyObject
   2:   {
   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 SimpleDP
  11:       {
  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:             else
  19:                 Assert.Fail("Wrong sort of property" + e.Current.Property);
  20:         }
  21:         //count为2
  22:         Assert.AreEqual(2, count);
  23:     }

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

 

0
0
标签:MONO .net WPF

.NET技术热门文章

    .NET技术最新文章

      最新新闻

        热门新闻