为了方便分析和向读者阐述,本文部分所援引的微软源代码有大幅的删减或更改,只保留了足以解释问题的关键行
依赖属性
DependencyProperty
静态hash表用于注册依赖属性
// 初始化
private static Hashtable PropertyFromName = new Hashtable();
// 注册一个依赖属性
private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
FromNameKey key = new FromNameKey(name, ownerType);
DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
lock (Synchronized)
{
PropertyFromName[key] = dp;
}
}
其中,FromNameKey key
完全由字符串生成:
_name = name;
_ownerType = ownerType;
_hashCode = _name.GetHashCode() ^ _ownerType.GetHashCode();
那么,DependencyProperty实例本身的HashCode是否就是FromNameKey key呢?不是的:
public override int GetHashCode()
{
return GlobalIndex;
}
这个GlobalIndex是个什么来头呢?
GlobalIndexMask = 0x0000FFFF;
public int GlobalIndex
{
get { return (int) (_packedData & Flags.GlobalIndexMask); }
}
也就是说它是_packedData
的低4位,注意这里隐含了一层意思:GlobalIndex
是有上限的,即32位无符号整型的最大值。继续追溯_packedData
的由来:
IsValueType = 0x00010000,
IsFreezableType = 0x00020000,
IsStringType = 0x00040000,
IsObjectType = 0x00400000,
private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
Flags packedData;
lock (Synchronized)
{
packedData = (Flags) GetUniqueGlobalIndex(ownerType, name);
RegisteredPropertyList.Add(this);
}
if (propertyType.IsValueType)
{
packedData |= Flags.IsValueType;
}
if (propertyType == typeof(object))
{
packedData |= Flags.IsObjectType;
}
if (typeof(Freezable).IsAssignableFrom(propertyType))
{
packedData |= Flags.IsFreezableType;
}
if (propertyType == typeof(string))
{
packedData |= Flags.IsStringType;
}
_packedData = packedData;
}
追溯关键函数GetUniqueGlobalIndex
internal static int GetUniqueGlobalIndex(Type ownerType, string name)
{
// Prevent GlobalIndex from overflow. DependencyProperties are meant to be static members and are to be registered
// only via static constructors. However there is no cheap way of ensuring this, without having to do a stack walk. Hence
// concievably people could register DependencyProperties via instance methods and therefore cause the GlobalIndex to
// overflow. This check will explicitly catch this error, instead of silently malfuntioning.
if (GlobalIndexCount >= (int)Flags.GlobalIndexMask)
{
if (ownerType != null)
{
throw new InvalidOperationException(SR.Get(SRID.TooManyDependencyProperties, ownerType.Name + "." + name));
}
else
{
throw new InvalidOperationException(SR.Get(SRID.TooManyDependencyProperties, "ConstantProperty"));
}
}
// Covered by Synchronized by caller
return GlobalIndexCount++;
}
看到这里我们明白了,GlobalIndex
可以简单理解为类内维护的一个每次增加1的整数,它和FromNameKey key
一样可以作为每个依赖属性实例的唯一标识符,只不过生成的方式和作用不同,至于它们各自在什么场合用于什么目的,接下来再分析。
依赖对象
我们知道,依赖属性必须在依赖对象中使用,才能发挥它真正的作用。对于一个依赖属性SomeProperty
,假如有1000个依赖对象实例,它们共用同一个SomeProperty
,这很好理解,因为SomeProperty
是static
类型的。那么问题来了,我们总是能够通过依赖对象实例读写依赖属性的值,这是怎么实现的呢?
读取依赖属性的值
/// <summary>
/// Retrieve the value of a property
/// </summary>
/// <param name="dp">Dependency property</param>
/// <returns>The computed value</returns>
public object GetValue(DependencyProperty dp)
{
return GetValueEntry(
LookupEntry(dp.GlobalIndex),
dp,
null,
RequestFlags.FullyResolved).Value;
}
不同的依赖实例是怎么找到属于自己的那个SomeProperty
的值的?看来我们还需要继续挖掘,暂时看到有两种可能性:
LookupEntry
做了手脚GetValueEntry
的实现做了手脚
先看第一种可能性:
internal EntryIndex LookupEntry(int targetIndex)
{
int checkIndex;
uint iLo = 0;
uint iHi = EffectiveValuesCount;
if (iHi <= 0)
{
return new EntryIndex(0, false /* Found */);
}
// Do a binary search to find the value
while (iHi - iLo > 3)
{
uint iPv = (iHi + iLo) / 2;
checkIndex = _effectiveValues[iPv].PropertyIndex;
if (targetIndex == checkIndex)
{
return new EntryIndex(iPv);
}
if (targetIndex <= checkIndex)
{
iHi = iPv;
}
else
{
iLo = iPv + 1;
}
}
// Now we only have three values to search; switch to a linear search
do
{
checkIndex = _effectiveValues[iLo].PropertyIndex;
if (checkIndex == targetIndex)
{
return new EntryIndex(iLo);
}
if (checkIndex > targetIndex)
{
// we've gone past the targetIndex - return not found
break;
}
iLo++;
}
while (iLo < iHi);
return new EntryIndex(iLo, false /* Found */);
}
注意它的实参是dp.GlobalIndex
,这对于SomeProperty
来说,在不同的依赖对象中都是同一个值。函数中有一个变量_effectiveValues
值得关注,它是一个私有字段:
// 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;
注释中写道:_effectiveValues
为此依赖对象实例维护了依赖属性的GlobalIndex
,那么我们可以初步推测,每个依赖对象实例都维护了一个EffectiveValueEntry
数组,并且它的长度是变长的?虽然数组类型在初始化时必须指定长度,但我们总是可以通过new
一个新的数组配合复制原数组内容来增加它的长度呀!
进入EffectiveValueEntry
内部,看看它存储了哪些重要信息
public int PropertyIndex
{
get { return _propertyIndex; }
set { _propertyIndex = (short)value; }
}
/// <summary>
/// If HasModifiers is true then it holds the value
/// else it holds the modified value instance
/// </summary>
internal object Value
{
get { return _value; }
set {
_value = value;
IsDeferredReference = value is DeferredReference;
Debug.Assert(value is DeferredReference == IsDeferredReference);
}
}
结合LookupEntry
的实现,可以确定PropertyIndex
就是前面提到的依赖属性的GlobalIndex
,而Value
不妨推测它就是每个依赖对象对应的依赖属性的实际值,在分析可能性2的时候再来证实。把目光重新聚焦在LookupEntry
函数上,它的返回值类型是EntryIndex
,看一下它是什么数据类型:
internal struct EntryIndex
{
public EntryIndex(uint index)
{
// Found is true
_store = index | 0x80000000;
}
public EntryIndex(uint index, bool found)
{
_store = index & 0x7FFFFFFF;
if (found)
{
_store |= 0x80000000;
}
}
public bool Found
{
get { return (_store & 0x80000000) != 0; }
}
public uint Index
{
get { return _store & 0x7FFFFFFF; }
}
private uint _store;
}
Found
用于标识对于当前依赖对象实例,要读取的依赖属性是否存在于_effectiveValues
,而Index
则是通过二分搜索确定的依赖属性在_effectiveValues
中的index。看来,LookupEntry
函数与依赖对象实例确实产生了一定联系。
看到这里,我们可以大胆推测一下:依赖对象的写依赖属性函数,应该是向_effectiveValues
插入或更改了一条数据。这个推测的求证过程按下不表,后面在剖析写函数的时候再拾起来
LookupEntry
函数被分析得差不多了,该轮到可能性2中的GetValueEntry
了,先关注它的返回值类型EffectiveValueEntry
,这是我们之前分析过的类型,它的属性PropertyIndex
我们之前已经确定了就是GlobalIndex
,而Value
属性的含义我们之前仅仅是做了猜测,这里我们进行证实。还记得我们现在分析的是GetValue
函数吗?它返回的是一个EffectiveValueEntry
的Value
属性,而GetValue
的作用是返回依赖对象实例的依赖属性的值,那么我们现在可以放心地说:EffectiveValueEntry
的Value
属性正是依赖属性在依赖对象中的个性值了。
来看一下GetValueEntry
的函数体(注意:本文对原函数进行了大量的裁剪,仅为说明函数主要功能,严谨的读者应阅读微软源代码):
/// <summary>
/// This overload of GetValue returns UnsetValue if the property doesn't
/// have an entry in the _effectiveValues. This way we will avoid inheriting
/// the default value from the parent.
/// </summary>
internal EffectiveValueEntry GetValueEntry(
EntryIndex entryIndex,
DependencyProperty dp,
PropertyMetadata metadata,
RequestFlags requests)
{
EffectiveValueEntry entry;
#region Code block#1
if (entryIndex.Found)
{
return _effectiveValues[entryIndex.Index];
}
else
{
return EffectiveValueEntry.CreateDefaultValueEntry(dp, dp.DefaultMetadata.DefaultValue);
}
#endregion
}
也就是说,如果依赖对象内部已经维护了针对依赖属性的个性值,则返回这个值;若没有,则返回一个默认值(实际上,返回默认值也只是一种路径,源代码中做了更为复杂的分类讨论)。
写入依赖属性的值
再回顾一下我们之前的推测:写入依赖属性就是向依赖对象内部的_effectiveValues
插入或更改了一条数据,现在来看一下事实是否确实如此?看一下代码:
/// <summary>
/// Sets the local value of a property
/// </summary>
/// <param name="dp">Dependency property</param>
/// <param name="value">New local value</param>
public void SetValue(DependencyProperty dp, object value)
{
// Cache the metadata object this method needed to get anyway.
PropertyMetadata metadata = SetupPropertyChange(dp);
// Do standard property set
SetValueCommon(dp, value, metadata, false /* coerceWithDeferredReference */, false /* coerceWithCurrentValue */, OperationType.Unknown, false /* isInternal */);
}
真正的实现要看SetValueCommon
:
private void SetValueCommon(
DependencyProperty dp,
object value,
PropertyMetadata metadata,
bool coerceWithDeferredReference,
bool coerceWithCurrentValue,
OperationType operationType,
bool isInternal)
{
// 此处省略很多代码,直接看核心的调用函数UpdateEffectiveValue
UpdateEffectiveValue(
entryIndex,
dp,
metadata,
oldEntry,
ref newEntry,
coerceWithDeferredReference,
coerceWithCurrentValue,
operationType);
}
由于要引证我们之前的猜测,我们必须在UpdateEffectiveValue
中找到修改_effectiveValues
的代码,这里的修改行为有两种:
- 在位修改,即_effectiveValues中已经有了依赖属性的local value
- 新建
_effectiveValues
实例,然后进行后续一系列配套操作
我们成功地找到一个函数,它完美地对应了以上两种修改行为:
// insert the given entry at the given index
// this function assumes that entryIndex is at the right
// location such that the resulting list remains sorted by EffectiveValueEntry.PropertyIndex
private void InsertEntry(EffectiveValueEntry entry, uint entryIndex)
{
uint effectiveValuesCount = EffectiveValuesCount;
if (effectiveValuesCount > 0)
{
if (_effectiveValues.Length == effectiveValuesCount)
{
int newSize = (int) (effectiveValuesCount * (IsInPropertyInitialization ? 2.0 : 1.2));
if (newSize == effectiveValuesCount)
{
newSize++;
}
EffectiveValueEntry[] destEntries = new EffectiveValueEntry[newSize];
Array.Copy(_effectiveValues, 0, destEntries, 0, entryIndex);
destEntries[entryIndex] = entry;
Array.Copy(_effectiveValues, entryIndex, destEntries, entryIndex + 1, effectiveValuesCount - entryIndex);
_effectiveValues = destEntries;
}
else
{
Array.Copy(_effectiveValues, entryIndex, _effectiveValues, entryIndex + 1, effectiveValuesCount - entryIndex);
_effectiveValues[entryIndex] = entry;
}
}
else
{
if (_effectiveValues == null)
{
_effectiveValues = new EffectiveValueEntry[EffectiveValuesInitialSize];
}
_effectiveValues[0] = entry;
}
EffectiveValuesCount = effectiveValuesCount + 1;
}
按照我们的构想,UpdateEffectiveValue
应该调用了InsertEntry
,是这样吗?让我们看一下函数调用链(自下而上调用):
InsertEntry
SetEffectiveValue
UpdateEffectiveValue
果真如我们所猜测的那样!
总结
到这里,WPF依赖属性源码剖析就结束了,我们清楚地看到,作为static
的依赖属性,是怎么在依赖对象实例中被读写的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。