这篇主要介绍依赖属性的内存使用和存取方面的知识。内容主要来自书籍《深入浅出WPF》。
1. 依赖属性对内存的使用
首先思考这样一个问题,TextBox有100多个属性,而常用的也就Text,而在创建Button实例的时候,其他的属性也是需要占用内存资源的,是不是很浪费。依赖属性是依赖在其他属性上的,而自身是不占内存空间的,减少了内存资源的开销。
传统的.NET开发中,一个对象所占作用的内存空间在调用new操作符进行实例化的时候就已经决定了,而WPF允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间)、只保留在需要用到数据时能够获得默认值、借用其他对象数据或者实时分配空间的能力——这种对象就称为依赖对象(Dependency Object)而它这种实时获取数据的能力则依靠依赖属性(Dependency Property)来实现。WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来,才能形成完整的Binding目标被数据所驱动。
2.依赖属性存取秘密
1) 注册方法DependencyProperty.Register()
DependencyProperty类具有这样一个成员:privatestatic Hashtable PropertyFromName = new Hashtable();这个Hashtable就是用来注册DependencyProperty实例的地方。private static DependencyProperty RegisterCommon(string name,Type propertyType,Type ownerType,PropertyMetadata defaultMetadata,ValidateValueCallback validateValueCallback) { FromNameKey key = new FromNameKey(name,ownerType); lock (Synchronized) { //防止重复注册 if (PropertyFromName.Contains(key)) { throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered,name,ownerType.Name)); } } //… DependencyProperty dp = new DependencyProperty(name,propertyType,ownerType,defaultMetadata,validateValueCallback); //… lock (Synchronized) { PropertyFromName[key] = dp; } //… return dp; }
可以看出,RegisterCommon方法的前4个参数与Register方法一致。
看了RegisterCommon这个方法,便能了解DependencyProperty对象的创建和注册过程:创建一个DependencyProperty实例并用它的CLR属性名和宿主类型名生成hash code,最后把hash code和DependencyProperty实例作为Key-Value对存入全局的、名为PropertyFromName的Hashtable中。这样,WPF属性系统通过CLR属性名和宿主类型名就可以从这个全局的Hashtable中检索出对应的DependencyProperty实例。最后,生成的DependencyProperty实例被当作返回值交还。
2)存值方法SetValue()和取值方法GetValue()
public object GetValue(DependencyProperty dp) { // Do not allow foreign threads access. // (This is a noop if this object is not assigned to a Dispatcher.) // this.VerifyAccess(); if (dp == null) { throw new ArgumentNullException("dp"); } // Call Forwarded return GetValueEntry( LookupEntry(dp.GlobalIndex),dp,null,RequestFlags.FullyResolved).Value; }
核心内容是 return 语句,展开为:
EntryIndex entryIndex=LookupEntry(dp.GlobalIndex); EffectiveValueEntry valueEntry=GetValueEntry(entryIndex,RequestFlags.FullyResolved); return valueEntry.Value;
这几句话代码中屡次出现了Entry这个词,Entry是“入口”的意思。WPF的依赖属性系统在存放值的时候会把每个有效值存放在一个“小房间”里,每个“小房间”都有自己的入口——检索算法只要找到这个入口、走进入口就能拿到依赖属性的值。这里说的“小房间”实际上就是EffectiveValueEntry类的实例。EffectiveValueEntry的所有构造器都包含一个DependencyProperty类型的参数,换句话说,每个EffectiveValueEntry都关联着一个DependencyProperty。EffectiveValueEntry类具有一个名为PropertyIndex的属性,这个属性的值实际上就是与之关联的DependencyProperty的GlobalIndex属性值(就是DependencyProperty实例的哈希值)。
在DependencyObject类的源码中可以找到这样一个成员变量:
// The cache of effective values for this DependencyObject // This is an array sorted by DP.GlobalIndex. This ordering is // maintained via an insertion sort algorithm. private EffectiveValueEntry[] _effectiveValues;
这个数组依每个成员的PropertyIndex属性值进行排序,对这个数组的操作(如插入、删除和排序等)由专门的算法来完成。正是这个数组向我们提示了依赖属性存储值的秘密——每个DependencyObject实例都自带一个EffectiveValueEntry类型数组,当某个依赖属性的值要被读取时,算法就从这个数组中去检索值,如果数组中没有包含这个值,算法就会返回依赖属性的默认值(这个值由依赖属性的DefaultMetadata来提供)。
至此,我们了解到,那个被static修饰的依赖属性对象的作用是用来检索真正的属性值而不是存储值;被用来检索键值的实际上是依赖属性的GlobalIndex属性(本质上是hash code,而hash code又由其CLR包装器名和宿主类型名共同决定),为了保证GlobalIndex属性值的稳定性,所以声明时用readonly关键词修饰。
- 检查值是不是DependencyProperty.UnsetValue,如果是,说明调用者的意图是清空现有值。此时程序调用ClearValueCommon方法来情况现有值。
- 检查EffectiveValueEntry数组中是否已经存在相应依赖属性的位置,如果有则把旧值改写为新值,如果没有则新建EffectiveValueEntry对象并存储新值。这样,只有被用到的值才会被放进这个列表,借此,WPF系统用算法(时间)换取了对内存(空间)的节省。
- 调用UpdateEffectiveValue对新值做一些相应处理。
作者:FoolRabbit
出处:http://blog.csdn.net/rabbitsoft_1987 欢迎任何形式的转载,未经作者同意,请保留此段声明!