WPF学习——依赖项属性(3)

本系列文章是从“@东邪独孤”博客中转过来的,本想着直接拿链接过来,但是如果他删了博客就没有了,文章写得很棒,一下就理解了依赖项属性。

原文地址:http://blog.csdn.net/tcjiaan/article/details/6940228


上一文中,我们分析了依赖项属性的注册和定义方法,并解释了依赖项属性的注册过程,但是,有一个疑问会困惑着我们,既然依赖项属被声明为静态只读字段,那为什么它的值可以被改变呢?难道你不觉得很奇怪吗?

微软的葫芦里到底卖的什么药呢?我们来看看。

前文中我们提到过,设置依赖项属性的值使用SetValue方法,那好,我们就从SetValue方法入手。

SetValue方法的定义如下:

public void SetValue(DependencyProperty dp, object value)  
{  
    base.VerifyAccess();  
    PropertyMetadata metadata = this.SetupPropertyChange(dp);  
    this.SetValueCommon(dp, value, metadata, false, OperationType.Unknown, false);  
} 

VerifyAccess方法是用于检查可访问性,这个不管它,重点是看 SetValueCommon 方法,它才是真正设置值的方法,好,继续跟入,看看SetValueCommon方法的定义:

private void SetValueCommon(DependencyProperty dp, object value, PropertyMetadata metadata, bool coerceWithDeferredReference, OperationType operationType, bool isInternal)  
{  
    if (this.IsSealed)  
    {  
        throw new InvalidOperationException(SR.Get("SetOnReadOnlyObjectNotAllowed", new object[] { this }));  
    }  
    Expression expr = null;  
    DependencySource[] newSources = null;  
    EntryIndex entryIndex = this.LookupEntry(dp.GlobalIndex);  
    if (value == DependencyProperty.UnsetValue)  
    {  
        this.ClearValueCommon(entryIndex, dp, metadata);  
    }  
    else  
    {  
        EffectiveValueEntry entry;  
        EffectiveValueEntry entry2;  
        bool flag = false;  
        bool flag2 = value == ExpressionInAlternativeStore;  
        if (!flag2)  
        {  
            bool flag3 = isInternal ? dp.IsValidValueInternal(value) : dp.IsValidValue(value);  
            if (!flag3 || dp.IsObjectType)  
            {  
                expr = value as Expression;  
                if (expr != null)  
                {  
                    if (!expr.Attachable)  
                    {  
                        throw new ArgumentException(SR.Get("SharingNonSharableExpression"));  
                    }  
                    newSources = expr.GetSources();  
                    ValidateSources(this, newSources, expr);  
                }  
                else  
                {  
                    flag = value is DeferredReference;  
                    if (!flag && !flag3)  
                    {  
                        throw new ArgumentException(SR.Get("InvalidPropertyValue", new object[] { value, dp.Name }));  
                    }  
                }  
            }  
        }  
        if (operationType == OperationType.ChangeMutableDefaultValue)  
        {  
            entry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Default) {  
                Value = value  
            };  
        }  
        else  
        {  
            entry = this.GetValueEntry(entryIndex, dp, metadata, RequestFlags.RawEntry);  
        }  
        object localValue = entry.LocalValue;  
        Expression expression2 = null;  
        Expression expression3 = null;  
        if (entry.HasExpressionMarker)  
        {  
            if (expr == null)  
            {  
                expression3 = _getExpressionCore(this, dp, metadata);  
            }  
            if (expression3 != null)  
            {  
                localValue = expression3;  
                expression2 = expression3;  
            }  
            else  
            {  
                localValue = DependencyProperty.UnsetValue;  
            }  
        }  
        else  
        {  
            expression2 = entry.IsExpression ? (localValue as Expression) : null;  
        }  
        bool flag5 = false;  
        if ((expression2 != null) && (expr == null))  
        {  
            if (flag)  
            {  
                value = ((DeferredReference) value).GetValue(BaseValueSourceInternal.Local);  
                flag = false;  
            }  
            flag5 = expression2.SetValue(this, dp, value);  
            entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);  
        }  
        if (flag5)  
        {  
            if (entryIndex.Found)  
            {  
                entry2 = this._effectiveValues[entryIndex.Index];  
            }  
            else  
            {  
                entry2 = EffectiveValueEntry.CreateDefaultValueEntry(dp, metadata.GetDefaultValue(this, dp));  
            }  
        }  
        else  
        {  
            entry2 = new EffectiveValueEntry(dp, BaseValueSourceInternal.Local);  
            if ((expression2 != null) && (expression2 != expression3))  
            {  
                DependencySource[] sources = expression2.GetSources();  
                UpdateSourceDependentLists(this, dp, sources, expression2, false);  
                expression2.OnDetach(this, dp);  
                entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);  
            }  
            if (expr == null)  
            {  
                entry2.IsDeferredReference = flag;  
                entry2.Value = value;  
                entry2.HasExpressionMarker = flag2;  
            }  
            else  
            {  
                this.SetEffectiveValue(entryIndex, dp, dp.GlobalIndex, metadata, expr, BaseValueSourceInternal.Local);  
                object defaultValue = metadata.GetDefaultValue(this, dp);  
                entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);  
                this.SetExpressionValue(entryIndex, defaultValue, expr);  
                UpdateSourceDependentLists(this, dp, newSources, expr, true);  
                expr.MarkAttached();  
                expr.OnAttach(this, dp);  
                entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);  
                entry2 = this.EvaluateExpression(entryIndex, dp, expr, metadata, entry, this._effectiveValues[entryIndex.Index]);  
                entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);  
            }  
        }  
        this.UpdateEffectiveValue(entryIndex, dp, metadata, entry, ref entry2, coerceWithDeferredReference, operationType);  
    }  
}  

第一个引起我注意的是DependencySource类,它封装了依赖性对象和依赖性属性,并分别为两个只读属性。

还有一个比较核心的类就是EffectiveValueEntry.

internal EffectiveValueEntry(DependencyProperty dp, BaseValueSourceInternal valueSource)  
{  
    this._propertyIndex = (short) dp.GlobalIndex;  
    this._value = DependencyProperty.UnsetValue;  
    this._source = (FullValueSource) valueSource;  
}  

FullValueSource是一个枚举,如果我没猜错的话,它就是用来标识属性的来源,它有以下几个值:

[FriendAccessAllowed]  
internal enum FullValueSource : short  
{  
    HasExpressionMarker = 0x100,  
    IsAnimated = 0x20,  
    IsCoerced = 0x40,  
    IsDeferredReference = 0x80,  
    IsExpression = 0x10,  
    ModifiersMask = 0x70,  
    ValueSourceMask = 15  
} 

1、通过XAML标记语言来改变属性值,如:

<TextBox x:Name="txt" Text="这是一个测试" />

这里就通过XAML标记为 Text属性赋了值。

 

2、受动画影响而改变的值。比如我画一个矩形,在动画面板中我让它演示长达6秒钟的动画,这期间,矩形的X坐标从20变为80,这个值就是通过动画来设置属性值,这个值是不会提交到属性更改,只是临时更改,动画停止后,属性值将会恢复为原来的值。

 

3、强制设置值。这个概念很奇怪,可以把它理解为对属性的输入值进行类型转换,我们知道,WPF里面提供了许多XXXXConvertor,毕竟我们在XAML标记中只能输入文本值,比如颜色,我们都会输入Red这样的值,其实在后台,运行库把字符串的值转换为画刷实例,再向属性赋值。

 

4、被改变后的值,从跟踪的结果看,依赖项属性的值都有N个版本,分别为不同的变量来保存,以便做验证和对比。

 

EffectiveValueEntry里面就是保存依赖项属性的全局索引和属性值,继续反编译,发现了一个InsertEntry方法,比较长,代码我不帖,只帖签名。

private void InsertEntry(EffectiveValueEntry entry, uint entryIndex)

两个参数都很好理解,第二个参数就是数组的索引,那么,是哪个数组的索引呢?接着反编译,看到DendencyObject类有一个内部字段,定义如下:

private EffectiveValueEntry[] _effectiveValues;

对,这下找到了,就是一个EffectiveValueEntry的数组,好了,不用往下跟了,到这里基本上可以看出依赖项属性是如何保存它的值了,原理如下:

 

每个DendencyObject类的实例都会创建一个专门的数组,数组中的每个元素分别标识着该类型的一个依赖项属性,例如我定义了一个类A,A里面定义了3个依赖项属性A.kk,A.cc,A.ff,这样当A类被分配到内存中实例化的时候,创建一个EffectvieValueEntry数组,而每个EffectiveValueEntry对应着一个属性,保存着该属性的不同版本的值,A类型有3个依赖项属性,所以对应的EffectiveEntry数组就有3个元素。

那么,CLR如何知道哪个EffectiveEntry对应着哪个依赖项属性呢?前面说过,每个依赖项属性都会以哈希值的键保存到一个全局哈希表中,所以,只要查找出对应键就可以唯一地标识依赖项属性了。

 

既然知道了如何设置值,那么,对于如何获取值就更好办了,过程刚好相反。

public object GetValue(DependencyProperty dp)  
{  
    base.VerifyAccess();  
    if (dp == null)  
    {  
        throw new ArgumentNullException("dp");  
    }  
    return this.GetValueEntry(this.LookupEntry(dp.GlobalIndex), dp, null, RequestFlags.FullyResolved).Value;  
} 

看到最后一行,其实返回了一个EffectiveValueEntry类型的变量,再从中取出Value属性的值,这个值其实就是对应类型的依赖项属性的值。

 

那么,EffectiveValueEntry数组中的元素是在哪儿赋值的呢?我翻遍了整个DependencyObject类也没有找到赋值的语句,这时候我突然想起刚才的SetValue方法,注意到这几行:

if (operationType == OperationType.ChangeMutableDefaultValue)  
 {  
     entry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Default) {  
         Value = value  
     };  
 }  
 else  
 {  
     entry = this.GetValueEntry(entryIndex, dp, metadata, RequestFlags.RawEntry);  
 }

如果要设置的值的等于默认,还记得我们调用Register注册依赖项属性时,最后一个参数,它是一个PropertyMetaData类型,它封装了依赖项属性的默认值,也就是元数据,元数据说白了就初始值,“元”在汉语中有“首”,“开始”,“第一”等意思,如“XX元年”、“公元2002年”等。

所以,上面的代码是:如果设置的值与元数据一致,就不用赋值了,直接从元数据里面,这样可以免去了在赋值过程发生的“拆箱”和“装箱”行为消耗性能;

如果设置的值不是元数据(默认值),那么就动态创建一个EffectvieValueEntry类的实例,并保存当前值。

因此,EffectiveValueEntry数组是动态分配内存的,就是说,如果你不对属性进行赋值,那就不创建EffectiveValueEntry实例,因为依赖项属性在静态注册时就带了默认值。

 

因此,对比传统的面向对象属性系统,WPF的依赖项属性有以下优点:

1、类被实例化时不对属性进行初始化,大大节省了内存空间。

在传统的属性系统中,比如一个类B有1000个属性,每个属性都是int类型,而整型每个实例占用4个字节的内存,初台化1000个属性,就等于分配1000个整型的有效内存,也就是说,B类实例化后将占用 1000 * 4 = 4000字节的内存,如果我的类有100000个字符串的属性,而这些属性都是保存中文字符,你可想象有多么恐怖!

WPF的依赖项属性系统对每个类初始化时并不初始化属性,而是等到运行时需要设置才进行分配,这样一来,就大大节省了内存占用率。

 

2、更高的灵活性。由于依赖项属性的声明是静态的,也就是说,它存在于整个应用程序生命命周期内,它并不属于特定类,如Content属性,它可以属于Canvas类,也可以属于DockPanel类,只要在全局哈希表中键值不重复即可。

还有就是附加属性,附加属性其实也是依赖项属性,比如一个会计的职责是处理帐目和财务相关的事情,有时候质量控制部门比较忙,财务部门的职责就多了一个检测的任务,但这个职责并不是财务人员所必须的,只是在需要时才存在。

附加属性就是这样,可以把其它类的属性附加到当前类的属性列表,而这些属性并不是必须的,需要时就存在,不需要时就不存在,如DockPanel类的Dock就是附加在其子元素中,因为DockPanel可能放N个元素,你无法用一个值来固定它们的位置,可能一些元素Top,或者有些元素Left,这样一来,只能把Dock属性附加在它们身上,按具体需要来设置。

 

3、继承性。XAML中元素的属性值可以从其容器标记或父节点继承而来的属值。如路由事件的泡冒行为就是一个典型。DockPanel里面放置了4个按钮,我只需在DockPanel的XAML声明中加入Button.Click="Button_Click",这样,子元节点在的所有按钮都会捕捉该事件,并调用同一个事件处理过程,这在传统的WinForm中是做不到的。

 

4、本地值。动画和3D变换等会改变对象的属性,但是,这些改变是临时,动画停止或删除后,该改变就不存在了。

 

5、数据绑定。依赖项自身具备实时更新功能,不需要我们手动去编写代码实现;WPF的数据绑定不仅仅在数据源,资源、样式、其它元素的属性值都可以实时更新。

 

6、资源。包括动态资源和静态资源的引用。这个我不多说了,请自行参考MSDN,上面都详细介绍了。

 

……

 

但是,依赖项属性并不是没有缺点的,它的缺点正是来自于它的优点。

由于依赖项属性是静态只读字段,所以应用程序启动时就必须创建所有的依赖项属性(注意,是创建用DependencyProperty.Register方法注册的DependencyProperty,不包括属性值),这就需要时间了,这就是为什么WPF程序启动速度慢的原因,它是用“时间”来换取“空间”,据说,微软一直在改善这问题,到了.NET3.5 SP1的时候,就有了明显的改进。



项目源代码在这儿:http://download.csdn.net/detail/tingzhiyi/9472851

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值