Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能,这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。
这段是MSDN上对依赖属性(DependencyProperty)的描述。主要介绍了两个方面,WPF中提供了可用于扩展CLR属性的服务;被这个服务支持的属性称为依赖属性。
单看描述,云里雾里的,了解一个知识,首先要知道它产生的背景和为什么要有它,那么WPF引入依赖属性是为了解决什么问题呢?
属性是我们很熟悉的,封装类的字段,表示类的状态,编译后被转化为get_,set_方法,可以被类或结构等使用。 一个常见的属性如下:
@H_
404_24@
1: public class NormalObject
@H_
404_24@
2: {
@H_
404_24@
3: private string _unUsedField;
@H_
404_24@
4:
@H_
404_24@
5: private string _name;
@H_
404_24@
6: public string Name
@H_
404_24@
7: {
@H_
404_24@
8: get
@H_
404_24@
9: {
@H_
404_24@
10: return _name;
@H_
404_24@
11: }
@H_
404_24@
12: set
@H_
404_24@
13: {
@H_
404_24@
14: _name =
value;
@H_
404_24@
15: }
@H_
404_24@
16: }
@H_
404_24@
17: }
在面向对象的世界里,属性大量存在,比如Button,就大约定义了70-80个属性来描述其状态。那么属性的不足又在哪里呢?
当然,所谓的不足,要针对具体环境来说。拿Button来讲,它的继承树是 Button->ButtonBase->ContentControl->Control->FrameworkElement->UIElement->Visual->DependencyObject->…
每次继承,父类的私有字段都被继承下来。当然,这个继承是有意思的,不过以Button来说,大多数属性并没有被修改,仍然保持着父类定义时的 默认值。通常情况,在整个Button对象的生命周期里,也只有少部分属性被修改,大多数属性一直保持着初始值。每个字段,都需要占用4K等不等的内存, 这里,就出现了期望可以优化的地方:
- 因继承而带来的对象膨胀。每次继承,父类的字段都被继承,这样,继承树的低端对象不可避免的膨胀。
- 大多数字段并没有被修改,一直保持着构造时的默认值,可否把这些字段从对象中剥离开来,减少对象的体积。
根据前面提出的需求,依赖属性就应运而生了。一个简单的依赖属性的原型如下:
DependencyProperty:
1: public class DependencyProperty
@H_
404_24@
3: internal static Dictionary<
object,DependencyProperty> RegisteredDps =
new Dictionary<
object,DependencyProperty>();
@H_
404_24@
4: internal string Name;
@H_
404_24@
5: internal object Value;
@H_
404_24@
6: internal object HashCode;
@H_
404_24@
7:
@H_
404_24@
8: private DependencyProperty(
string name,Type propertyName,Type ownerType,
object defaultValue)
@H_
404_24@
9: {
@H_
404_24@
10: this.Name = name;
@H_
404_24@
11: this.Value = defaultValue;
@H_
404_24@
12: this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
@H_
404_24@
13: }
@H_
404_24@
14:
@H_
404_24@
15: public static DependencyProperty Register(
string name,Type propertyType,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none">
16: {
@H_
404_24@
17: DependencyProperty dp =
new DependencyProperty(name,propertyType,ownerType,defaultValue);
@H_
404_24@
18: RegisteredDps.Add(dp.HashCode,dp);
@H_
404_24@
19: return dp;
@H_
404_24@
20: }
@H_
404_24@
21: }
@H_
404_24@
22:
DependencyObject:
1: public class DependencyObject
@H_
404_24@
5: public static readonly DependencyProperty NameProperty =
@H_
404_24@
6: DependencyProperty.Register(
"Name",
typeof(
string),
typeof(DependencyObject),
string.Empty);
@H_
404_24@
8: public object GetValue(DependencyProperty dp)
@H_
404_24@
10: return DependencyProperty.RegisteredDps[dp.HashCode].Value;
@H_
404_24@
11: }
@H_
404_24@
12:
@H_
404_24@
13: public void SetValue(DependencyProperty dp,
object value)
@H_
404_24@
14: {
@H_
404_24@
15: DependencyProperty.RegisteredDps[dp.HashCode].Value =
value;
@H_
404_24@
16: }
@H_
404_24@
17:
@H_
404_24@
18: public string Name
@H_
404_24@
19: {
@H_
404_24@
20: get
@H_
404_24@
21: {
@H_
404_24@
22: return (
string)GetValue(NameProperty);
@H_
404_24@
23: }
@H_
404_24@
24: set
@H_
404_24@
25: {
@H_
404_24@
26: SetValue(NameProperty,
value);
@H_
404_24@
27: }
@H_
404_24@
28: }
@H_
404_24@
29: }
@H_
404_24@
30:
这里,首先定义了依赖属性DependencyProperty,它里面存储前面我们提到希望抽出来的字段。DP内部维护了一个全局的Map用 来储存所有的DP,对外暴露了一个Register方法用来注册新的DP。当然,为了保证在Map中键值唯一,注册时需要根据传入的名字和注册类的的 HashCode取异或来生成Key。这里最关键的就是最后一个参数,设置了这个DP的默认值。
然后定义了DependencyObject来使用DP。首先使用DependencyProperty.Register方法注册了一个新的 DP(NameProperty),然后提供了GetValue和SetValue两个方法来操作DP。最后,类似前面例子中的 NormalObject,同样定义了一个属性Name,和NormalObject的区别是,实际的值不是用字段来保存在 DependencyObject中的,而是保存在NameProperty这个DP中,通过GetValue和SetValue来完成属性的赋值取值操 作。
当然,作为一个例子,为了简洁,很多情况没有考虑,现在来测试一下是否解决了前面的问题。
新建两个对象,NormalObject和DependencyObject,在VS下打开SOS查看:
.load sos extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded !DumpHeap -stat -type NormalObject total 1 objects Statistics: MT Count TotalSize Class Name 009e30d0 1 16 DPDemonstration.NormalObject Total 1 objects !DumpHeap -stat -type DependencyObject total 1 objects Statistics: MT Count TotalSize Class Name 009e31a0 1 12 DPDemonstration.DependencyObject Total 1 objects
这里在对象中分别建立了一个_unUsedField的字段,.Net的GC要求对象的最小Size为12字节。如果对象的Size不足12字 节,则会自动补齐。默认的Object对象占用8字节,Syncblk(4字节)以及TypeHandle(4字节),为了演示方便,加入了一个 _unUsedField(4字节)来补齐。
这里,DependencyObject相比NormalObject,减少了_name的储存空间4字节。
再进一步
万里长征第一步,这个想法可以解决我们希望的问题,这个做法还不能让人接受。在这个实现中,所有DependencyObject共用一个DP,这个可以理解,但修改一个对象的属性后,所有对象的属性相当于都被修改了,这个就太可笑了。
所以对象属性一旦被修改,这个还是要维护在自己当中的,修改一下前面的DependencyObject,引入一个有效(Effective)的概念。
改进的DependencyObject,加入了_effectiveValues:
3: private List<EffectiveValueEntry> _effectiveValues =
new List<EffectiveValueEntry>();
@H_
404_24@
10: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
@H_
404_24@
11: if (effectiveValue.PropertyIndex != 0)
@H_
404_24@
12: {
@H_
404_24@
13: return effectiveValue.Value;
@H_
404_24@
14: }
@H_
404_24@
15: else
@H_
404_24@
16: {
@H_
404_24@
17: return DependencyProperty.RegisteredDps[dp.HashCode].Value;
@H_
404_24@
18: }
@H_
404_24@
19: }
@H_
404_24@
20:
@H_
404_24@
21: public void SetValue(DependencyProperty dp,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none">
22: {
@H_
404_24@
23: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
@H_
404_24@
24: if (effectiveValue.PropertyIndex != 0)
@H_
404_24@
26: effectiveValue.Value =
value;
@H_
404_24@
28: else
@H_
404_24@
29: {
@H_
404_24@
30: effectiveValue =
new EffectiveValueEntry() { PropertyIndex = dp.Index,Value =
value };
@H_
404_24@
31: _effectiveValues.Add(effectiveValue);
@H_
404_24@
32: }
@H_
404_24@
33: }
@H_
404_24@
34:
@H_
404_24@
35: public string Name
@H_
404_24@
36: {
@H_
404_24@
37: get
@H_
404_24@
38: {
@H_
404_24@
39: return (
string)GetValue(NameProperty);
@H_
404_24@
40: }
@H_
404_24@
41: set
@H_
404_24@
42: {
@H_
404_24@
43: SetValue(NameProperty,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none">
44: }
@H_
404_24@
45: }
@H_
404_24@
46: }