依赖属性之“风云再起”二
发布时间:2020-12-13 20:21:58 所属栏目:百科 来源:网络整理
导读:五. 引入测试驱动开发 1,引入概念 由于本篇的依赖属性体系是基于测试驱动开发完成的,所以我们就先来看一下什么叫测试驱动开发:测试驱动开发的基本思想就是在开发功能代码之前, 先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能
五. 引入测试驱动开发1,引入概念
由于本篇的依赖属性体系是基于测试驱动开发完成的,所以我们就先来看一下什么叫测试驱动开发:测试驱动开发的基本思想就是在开发功能代码之前, 先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循 环进行添加其他功能,直到完全部功能的开发。由于过程很长,在写的时候也省略了不少步骤,所以有些地方衔接不是那么的流畅,对此表示非常的抱歉!
2,注意事项
根据自身做项目使用TDD的一点微薄经验,总结了以下几个注意事项:
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的开发者,主要特性列举如下:
B,TestDriven.NET 3.0中的新特性:
C,兼容性
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 : DependencyObject 2: {
3: //注册依赖属性property1 4: public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1",typeof(string),255);">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: void TestMultipleRegisters() 4: {
5: //测试注册相同名的依赖属性 6: DependencyProperty.Register("p1",255);">typeof(ObjectPoker)); 7: DependencyProperty.Register(typeof(ObjectPoker)); 8: }
我们说到依赖属性系统,其实依赖属性要依附于DependencyObject才能成为真正的依赖属性系统。所以我们来测试一下 AddOwner,每一个Owner都有自己的元数据,这个时候我们需要完善OverrideMetadata方法,然而 OverrideMetadata方法需要用到PropertyMetadata类作为参数,同时需要调用PropertyMetadata类的 DoMerge方法,我们可以创建该类,然后结合Mock完成该操作。
void TestMultipleAddOwner()
//测试AddOwner,添加相同类型的Owner
6: ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker),255);">new PropertyMetadata()); 7: ObjectPoker.TestProp1.AddOwner(new PropertyMetadata()); 8: }
通过上面的测试用例以后,其实PropertyMetadata的原型已经具备了,然后我们要做的就是测试DependencyProperty的默认元数据和默认元数据的默认值。
2: void TestDefaultMetadata()
3: {
//测试默认元数据
5: DependencyProperty p;
6: p = DependencyProperty.Register("TestDefaultMetadata1",96);"> 7: Assert.IsNotNull(p.DefaultMetadata); 8:
9: //测试元数据的默认值 10: p = DependencyProperty.Register("TestDefaultMetadata2",255);">typeof(ObjectPoker),255);">new PropertyMetadata("hi")); 11: Assert.IsNotNull(p.DefaultMetadata);
12: Assert.AreEqual("hi",p.DefaultMetadata.DefaultValue); 13: }
我们都知道一个DependencyProperty可以拥有多个Owner,每个Owner之间的区别就是用PropertyMetadata,那么这里就给该DependencyProperty添加一个Owner,然后通过该Owner来获取元数据。
void TestAddOwnerNullMetadata()
//首先注册一个依赖属性,然后再AddOwner,最后根据新的Owner获取元数据
5: DependencyProperty p = DependencyProperty.Register("TestAddOwnerNullMetadata",255);">typeof(ObjectPoker)); 6: p.AddOwner(null); 7:
8: PropertyMetadata pm = p.GetMetadata(typeof(SubclassPoker)); 9: Assert.IsNotNull(pm);
10: }
通过上面的测试用例,我们牵涉到了OverrideMetadata方法,当然上面没有进行实现,这个时候我们可以来实现 OverrideMetadata这个方法,首先注册一个ObjectPoker类型的依赖属性,然后通过SubclassPoker来 OverrideMetadata。
//首先注册一个依赖属性,然后再OverrideMetadata
2: [Test]
3: [ExpectedException(typeof(ArgumentNullException))] 4: void TestOverrideMetadataNullMetadata() 5: {
6: //有Type但PropertyMetadata为null时,OverrideMetadata操作 7: DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullMetadata",96);"> 8: p.OverrideMetadata(null); 9: }
上面实现了OverrideMetadata的函数,但是只是简单实现,这里我们可以传入一个null类型的Type作为测试,当然测试不会通过,然后就修改代码直到测试通过吧!
typeof(ArgumentNullException))]
void TestOverrideMetadataNullType()
//当Type为null,OverrideMetadata操作
6: DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullType",96);"> 7: p.OverrideMetadata(null,96);"> 8: }
如果仔细分析DependencyProperty的源码,你会发现有一个DependencyPropertyKey类,这个类到底是干嘛的 呢?其实这个类的主要作用就是构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata,这里只是提供 了一个简单的封装,如果没有这个类,其他功能照样正常。
typeof(InvalidOperationException))]
void TestReadonlyOverrideMetadata()
//通过DependencyPropertyKey的方式OverrideMetadata
6: DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop1", 7: double), 8: 9: double.NaN)); 10: ro_key.DependencyProperty.OverrideMetadata(new PropertyMetadataPoker()); 11: }
最后我们来测试一样通过DependencyPropertyKey类来注册一个ReadOnly的依赖属性,然后进行OverrideMetadata,基本和上一个测试用例类似。
void TestReadonlyOverrideMetadataFromKey()
//通过DependencyPropertyKey的方式OverrideMetadata
5: DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop2", 6: double.NaN)); 9: ro_key.OverrideMetadata(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:
using System.Collections.Generic;
namespace System.Windows
sealed class DependencyProperty 6: {
7: //一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据 8: private Dictionary<Type,PropertyMetadata> metadataByType = new Dictionary<Type,PropertyMetadata>(); 9:
10: //声明一个UnsetValue 11: readonly object UnsetValue = new object (); 12:
13: //依赖属性私有构造函数,作为初始化操作,每个依赖属性在注册的时候都会调用并初始化数据 14: private DependencyProperty (bool isAttached,255);">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: bool ReadOnly { get; private set; } 28: public PropertyMetadata DefaultMetadata { get; private set; } 29: 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: int GlobalIndex { 36: get { throw new NotImplementedException (); } 37: }
38:
39: //传入ownerType增加Owner 40: public DependencyProperty AddOwner(Type ownerType) 41: {
42: return AddOwner (ownerType,255);">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: null; 61: }
62:
63: //获取元数据,依据该依赖属性 64: public PropertyMetadata GetMetadata(DependencyObject d) 65: {
66: if (metadataByType.ContainsKey (d.GetType())) 67: return metadataByType[d.GetType()]; 68: null; 69: }
70:
71: //获取元数据,依据dependencyObjectType 72: public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType) 73: {
74: if (metadataByType.ContainsKey (dependencyObjectType.SystemType)) 75: return metadataByType[dependencyObjectType.SystemType]; 76: null; 77: }
78:
79: //验证类型是否有效 80: bool IsValidType(object value) 81: {
82: return PropertyType.IsInstanceOfType (value); 83: }
84:
85: //验证值是否有效 86: bool IsValidValue(value) 87: {
88: if (!IsValidType (value)) 89: false; 90: if (ValidateValueCallback == null) 91: true; 92: return ValidateValueCallback (value); 93: }
94:
95: //重写元数据,使用PropertyMetadata类的DoMerge方法来操作 96: void OverrideMetadata(Type forType,PropertyMetadata typeMetadata) 97: {
98: if (forType == null) 99: new ArgumentNullException ("forType"); 100: null) 101: "typeMetadata"); 102:
103: if (ReadOnly) 104: new InvalidOperationException (String.Format ("Cannot override metadata on readonly property '{0}' without using a DependencyPropertyKey",Name)); 105:
106: typeMetadata.DoMerge (DefaultMetadata,255);">this,forType);
107: metadataByType.Add (forType,typeMetadata);
108: }
109:
110: //重写元数据,使用PropertyMetadata类的DoMerge方法来操作 111: void OverrideMetadata (Type forType,PropertyMetadata typeMetadata,DependencyPropertyKey key) 112: {
113: null) 114: "forType"); 115: null) 116: "typeMetadata"); 117:
118: typeMetadata.DoMerge (DefaultMetadata,forType);
119: metadataByType.Add (forType,typeMetadata);
120: }
121:
122: override string ToString () 123: {
124: return Name; 125: }
126:
127: //得到哈希值,区别不同的依赖属性,Name、PropertyType、OwnerType的哈希值取异 128: int GetHashCode () 129: {
130: return Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode(); 131: }
132:
133: //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type) 134: static DependencyProperty Register( 135: { 136: return Register(name,propertyType,ownerType,255);">null); 137: }
138:
139: //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据) 140: 141: PropertyMetadata typeMetadata) 142: {
143: null); 144: }
145:
146: //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托) 147: 148: PropertyMetadata typeMetadata, 149: ValidateValueCallback validateValueCallback)
150: {
151: null) 152: typeMetadata = new PropertyMetadata(); 153:
154: DependencyProperty dp = new DependencyProperty(false,name, 155: typeMetadata,validateValueCallback);
156: DependencyObject.register(ownerType,dp);
157:
158: dp.OverrideMetadata (ownerType,typeMetadata);
159:
160: return dp; 161: }
162:
163: //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type) 164: static DependencyProperty RegisterAttached( 165: { 166: return RegisterAttached(name,255);">null); 167: }
168:
169: //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据) 170: 171: PropertyMetadata defaultMetadata) 172: {
173: null); 174: }
175:
176: //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托) 177: 178: PropertyMetadata defaultMetadata, 179: ValidateValueCallback validateValueCallback)
180: {
181: DependencyProperty dp = true, 182: defaultMetadata,validateValueCallback);
183: DependencyObject.register(ownerType,dp);
184: return dp; 185: }
186:
187: //注册只读依赖属性,暂未实现 188: static DependencyPropertyKey RegisterAttachedReadOnly( 189: PropertyMetadata defaultMetadata) 190: {
191: new NotImplementedException("RegisterAttachedReadOnly(string name,PropertyMetadata defaultMetadata)"); 192: }
193:
194: //注册只读依赖属性,暂未实现 195: 196: PropertyMetadata defaultMetadata, 197: ValidateValueCallback validateValueCallback)
198: {
199: ); 200: }
201:
202: //注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据) 203: static DependencyPropertyKey RegisterReadOnly( 204: PropertyMetadata typeMetadata) 205: {
206: return RegisterReadOnly (name,255);">null); 207: }
208:
209: //注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托) 210: 211: PropertyMetadata typeMetadata, 212: ValidateValueCallback validateValueCallback)
213: {
214: DependencyProperty prop = Register (name,validateValueCallback);
215: prop.ReadOnly = true; 216: new DependencyPropertyKey (prop); 217: }
218:
219: }
220: }
通过前面的步骤,DependencyProperty已经完成,那么下面我们再来看一下DependencyObject类。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |