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

这篇主要介绍依赖属性的内存使用和存取方面的知识。内容主要来自书籍《深入浅出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 Forwarded
	return 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对新值做一些相应处理。



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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FoolRabbit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值