WPF依赖属性源码剖析

vancymoon

为了方便分析和向读者阐述,本文部分所援引的微软源代码有大幅的删减或更改,只保留了足以解释问题的关键行

依赖属性

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,这很好理解,因为SomePropertystatic类型的。那么问题来了,我们总是能够通过依赖对象实例读写依赖属性的值,这是怎么实现的呢?

读取依赖属性的值

/// <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的值的?看来我们还需要继续挖掘,暂时看到有两种可能性:

  1. LookupEntry做了手脚
  2. 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函数吗?它返回的是一个EffectiveValueEntryValue属性,而GetValue的作用是返回依赖对象实例的依赖属性的值,那么我们现在可以放心地说:EffectiveValueEntryValue属性正是依赖属性在依赖对象中的个性值了。

来看一下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的代码,这里的修改行为有两种:

  1. 在位修改,即_effectiveValues中已经有了依赖属性的local value
  2. 新建_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的依赖属性,是怎么在依赖对象实例中被读写的。

阅读 497
186 声望
0 粉丝
0 条评论
186 声望
0 粉丝
文章目录
宣传栏