依赖属性(Dependency Properties)进阶(二)

来源:互联网 发布:最伤感流行网络歌曲 编辑:程序博客网 时间:2024/06/05 03:12

这篇主要介绍依赖属性的内存使用和存取方面的知识。内容主要来自书籍《深入浅出WPF》。


1. 依赖属性对内存的使用

首先思考这样一个问题,TextBox100多个属性,而常用的也就Text,而在创建Button实例的时候,其他的属性也是需要占用内存资源的,是不是很浪费。依赖属性是依赖在其他属性上的,而自身是不占内存空间的,减少了内存资源的开销。

传统的.NET开发中,一个对象所占作用的内存空间在调用new操作符进行实例化的时候就已经决定了,而WPF允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间)、只保留在需要用到数据时能够获得默认值、借用其他对象数据或者实时分配空间的能力——这种对象就称为依赖对象(Dependency Object)而它这种实时获取数据的能力则依靠依赖属性(Dependency Property)来实现。WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来,才能形成完整的Binding目标被数据所驱动。


2. 依赖属性存取秘密

依赖属性使用的两个步骤:a)在Dependency Object派生类中声明 public static修饰的DependencyProperty成员变量,并使用DependencyProperty.Register方法(而不是new操作符)获得DependencyProperty的实例;b)使用DependencyObjectSetValueGetValue方法、借助DependencyProperty实例来存取值。

1) 注册方法DependencyProperty.Register()


DependencyProperty类具有这样一个成员:
privatestatic Hashtable PropertyFromName = new Hashtable();
这个Hashtable就是用来注册DependencyProperty实例的地方。
在源码中,所有的Register方法都会调用RegisterCommon方法:
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()


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 Forwardedreturn GetValueEntry(LookupEntry(dp.GlobalIndex),dp,null,RequestFlags.FullyResolved).Value;}

核心内容是return语句,展开为:
EntryIndex entryIndex=LookupEntry(dp.GlobalIndex);EffectiveValueEntry valueEntry=GetValueEntry(entryIndex,dp,null,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关键词修饰。
实际工作中,依赖属性的值除了可能存储在EffectiveValueEntry数组或其默认值提供外,还可以通过Style,Theme,上层元素继承等得到,其优先级在前面(依赖属性基础)已经说明。
理解GetValue方法后,SetValue方法也不在神秘。赋值的主要流程为:
  • 检查值是不是DependencyProperty.UnsetValue,如果是,说明调用者的意图是清空现有值。此时程序调用ClearValueCommon方法来情况现有值。
  • 检查EffectiveValueEntry数组中是否已经存在相应依赖属性的位置,如果有则把旧值改写为新值,如果没有则新建EffectiveValueEntry对象并存储新值。这样,只有被用到的值才会被放进这个列表,借此,WPF系统用算法(时间)换取了对内存(空间)的节省。
  • 调用UpdateEffectiveValue对新值做一些相应处理。

DependencyObject(依赖对象)和DependencyProperty(依赖属性)源代码


作者:FoolRabbit
出处:http://blog.csdn.net/rabbitsoft_1987
欢迎任何形式的转载,未经作者同意,请保留此段声明!

0 0