依赖属性的定义,分为3步(以PresentationFramework中的System.Windows.Controls.Button为例)
1. 声明依赖属性
public static readonly DependencyProperty IsDefaultProperty
2. 调用DependencyProperty.Register创建依赖属性实例
IsDefaultProperty = DependencyProperty.Register("IsDefault", typeof(bool), typeof(Button), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, new PropertyChangedCallback(Button.OnIsDefaultChanged)));
此例中,第一个参数是依赖属性名称,第一个参数是依赖属性的值类型,第三个参数为依赖属性所在的类型,第四个参数是可选的为依赖属性提供元数据。
3. 为依赖属性添加传统的CLR属性封装
public bool IsDefault { get { return (bool) base.GetValue(IsDefaultProperty); } set { base.SetValue(IsDefaultProperty, BooleanBoxes.Box(value)); } }
为什么
1. 声明依赖属性时为什么是public、static和readonly
按照惯例所有的依赖属性通常都是public, static并且以Property结尾。因为是public的所以需要使用readonly来防止第三方代码对依赖属性的意外修改。
2. DependencyProperty.Register的第一和第二个参数
第一个参数和第二个参数用来惟一确定一个依赖属性,换句话说WPF为每个依赖属性创建一个实例,该实例由依赖属性名称和其所在类型所决定,并由DependencyProperty.Register返回,可以从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("PropertyAlreadyRegistered", new object[] { name, ownerType.Name })); } }
你可以在上面的代码中看到PropertyFromName(第二行红色),PropertyFromName是一个私有的静态哈希表,用来存放使用DependencyProperty.Register注册到WPF对象层次结构中的所有(包括贡献依赖属性的所有类)依赖属性实例的静态引用。从上面代码可以看出,当name(第一个参数,依赖属性名称)和ownerType(第二参数,贡献依赖属性的类)确定时,惟一对应PropertyFromName中的一个值(即为依赖对象实例静态引用)。
3. DependencyProperty.Register的第四个参数
第四个参数包含描述依赖属性的元数据,定制WPF处理依赖属性的行为,提供属性值改变时的回调函数和属性值的有效性验证等。
4. 传统.Net属性封装
这一步并不是必须的, 应为GetValue和SetValue(后面将说明)是publish的,所以在代码中可以直接调用这两个函数(必须继承DependencyObject)。但是提供该封装可以在编程时方便使用,如果要用XAML属性中使用该依赖属性就一定要提供该封装。
它们是如何工作的
1. DependencyProperty.Register做了什么
先看一下它的反编译代码:
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback) { RegisterParameterValidation(name, propertyType, ownerType); PropertyMetadata defaultMetadata = null; if ((typeMetadata != null) && typeMetadata.DefaultValueWasSet()) { defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue); } DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback); if (typeMetadata != null) { property.OverrideMetadata(ownerType, typeMetadata); } return property; }
它调用了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("PropertyAlreadyRegistered", new object[] { name, ownerType.Name })); } } if (defaultMetadata == null) { defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType); } else { if (!defaultMetadata.DefaultValueWasSet()) { defaultMetadata.DefaultValue = AutoGenerateDefaultValue(propertyType); } ValidateMetadataDefaultValue(defaultMetadata, propertyType, name, validateValueCallback); } DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback); defaultMetadata.Seal(dp, null); if (defaultMetadata.IsInherited) { dp._packedData |= Flags.IsPotentiallyInherited; } if (defaultMetadata.UsingDefaultValueFactory) { dp._packedData |= Flags.IsPotentiallyUsingDefaultValueFactory; } lock (Synchronized) { PropertyFromName[key] = dp; } if (TraceDependencyProperty.IsEnabled) { TraceDependencyProperty.TraceActivityItem(TraceDependencyProperty.Register, dp, dp.OwnerType); } return dp; }
在RegisterCommon函数中第一行红色代码使用接收的依赖属性名称和所在类的名称创建了FromNameKey实例key;第二行红色代码检测key是否在PropertyFromName中,如果存在则抛异常(WPF只为类的每个依赖属性创建一个实例);如果key不存在,也即类的某个惟一命名依赖属性不存在,则第三行红色代码调用DependencyProperty的private构造函数为该依赖属性创建实例;最后第四行红色代码把创建的依赖属性加入到PropertyFromName中。
2. DependencyProperty的私有构造函数
private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback) { Flags uniqueGlobalIndex; this._metadataMap = new InsertionSortMap(); this._name = name; this._propertyType = propertyType; this._ownerType = ownerType; this._defaultMetadata = defaultMetadata; this._validateValueCallback = validateValueCallback; lock (Synchronized) { uniqueGlobalIndex = (Flags) GetUniqueGlobalIndex(ownerType, name); RegisteredPropertyList.Add(this); } if (propertyType.IsValueType) { uniqueGlobalIndex |= Flags.IsValueType; } if (propertyType == typeof(object)) { uniqueGlobalIndex |= Flags.IsObjectType; } if (typeof(Freezable).IsAssignableFrom(propertyType)) { uniqueGlobalIndex |= Flags.IsFreezableType; } if (propertyType == typeof(string)) { uniqueGlobalIndex |= Flags.IsStringType; } this._packedData = uniqueGlobalIndex; }
internal static int GetUniqueGlobalIndex(Type ownerType, string name) { if (GlobalIndexCount < 0xffff) { return GlobalIndexCount++; } if (ownerType != null) { throw new InvalidOperationException(SR.Get("TooManyDependencyProperties", new object[] { ownerType.Name + "." + name })); } throw new InvalidOperationException(SR.Get("TooManyDependencyProperties", new object[] { "ConstantProperty" })); }
从上面两段代码可以看出WPF为每个DependencyProperty实例创建了一个自增的索引uniqueGlobalIndex,并把该索引和DependencyProperty值类型(使用Flags枚举来表示)一起封装在_packedData中。从上面代码可以看出WPF至多支持同时创建0xffff(65535)个依赖属性。
3. DependencyObject
使用依赖属性的所有类都必须继承DependencyObject,该类定义了操作依赖属性的相关方法,如下面介绍的SetValue和GetValue。DependencyObject具有一个私有的实例字段_effectiveValues用于存放依赖属性值和对应的依赖属性索引,换句话说继承自DependencyObject的类的每个实例均维护着用于存放该类定义的通过DependencyProperty.Register注册到WPF基础结构的依赖属性值(注意不是依赖属性实例)的数组。该数组的元素为EffectiveValueEntry类型,包含依赖属性实例索引(上面已经说明所有的类实例共享一个依赖属性实例)和依赖属性值(因类的实例的不同而不同)的对应关系。
依赖属性和依赖属性值的存储方案如下图:
3.1 SetValue
这是由DependencyObject提供的实例方法,用于设置DependencyProperty在类实例的值。调用SetValue时WPF创建EffectiveValueEntry实例用于存放依赖属性值和依赖属性实例索引的对象关系并插入到_effectiveValues数组中,依赖属性值在_effectiveValues中是按照依赖属性的索引从小到大有序存放的(详细实现可查看DependencyObject类成员函数InsertEntry的反编译代码)。
3.2 GetValue
这是由DependencyObject提供的实例方法,用于获取DependencyProperty在类实例的值。调用GetValue时WPF根据提供的依赖属性实例索引在_effectiveValues中搜索对应的属性值,由于_effectiveValues是有序的,所以实现中使用二分法来提高搜索性能(详细实现可查看DependencyObject类成员函数LookupEntry的反编译代码)。
总结
将依赖属性从依赖属性的值上剥离,主要是为了性能上的考虑。一个WPF类可能使用几十上百个字段,并且在一次窗体呈现中该类可能被实例化不只一次(如一个Button包含有96个字段,且在一个窗体中可能包含很多个Button),如果使用传统CLR属性方式,则将为附加到字段实例上的本地化数据分配存储空间。假设控件每个字段的本地化数据大小平均为m,包含的字段数为f,控件被创建的次数为n,则需要的总空间为M = m * f * n,消耗的空间是直线上升的。使用依赖属性,由于依赖属性实例引用是静态的,且WPF只为依赖属性创建一个实例,所以实际所需要的空间只剩下为每个控件实例保存依赖属性值的空间(即M=0)。
引用
《Windows.Presentation.Foundation.Unleashed》
http://www.abhisheksur.com/2011/07/internals-of-dependency-property-in-wpf.html?_sm_au_=iVVjWL1ZNjHpkVpM