Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能,这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。

这段是MSDN上对依赖属性(DependencyProperty)的描述。主要介绍了两个方面,WPF中提供了可用于扩展CLR属性的服务;被这个服务支持的属性称为依赖属性。

单看描述,云里雾里的,了解一个知识,首先要知道它产生的背景和为什么要有它,那么WPF引入依赖属性是为了解决什么问题呢?

从属性说起

属性是我们很熟悉的,封装类的字段,表示类的状态,编译后被转化为get_,set_方法,可以被类或结构等使用。 一个常见的属性如下:

 public class NormalObject

   {

        private string _unUsedField;

        private string _name;

        public string Name

        {

            get

            {

               return _name;

           }

           set

           {

               _name = value;

           }

       }   

   }

  

在面向对象的世界里,属性大量存在,比如Button,就大约定义了70-80个属性来描述其状态。那么属性的不足又在哪里呢?

  当然,所谓的不足,要针对具体环境来说。拿Button来讲,它的继承树是 Button->ButtonBase->ContentControl->Control->FrameworkElement->UIElement->Visual->DependencyObject->…

  每次继承,父类的私有字段都被继承下来。当然,这个继承是有意思的,不过以Button来说,大多数属性并没有被修改,仍然保持着父类定义时的默认值。通常情况,在整个Button对象的生命周期里,也只有少部分属性被修改,大多数属性一直保持着初始值。每个字段,都需要占用4K等不等的内存,这里,就出现了期望可以优化的地方:

* 因继承而带来的对象膨胀。每次继承,父类的字段都被继承,这样,继承树的低端对象不可避免的膨胀。
    * 大多数字段并没有被修改,一直保持着构造时的默认值,可否把这些字段从对象中剥离开来,减少对象的体积。

依赖属性的原型
根据前面提出的需求,依赖属性就应运而生了。一个简单的依赖属性的原型如下:

DependencyProperty:

  public class DependencyProperty

    {

        internal static Dictionary<object, DependencyProperty> RegisteredDps = new Dictionary<object, DependencyProperty>();

        internal string Name;

        internal object Value;

        internal object HashCode;

     private DependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)

      {

           this.Name = name;

           this.Value = defaultValue;

           this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();

       }

       public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)

       {

           DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultValue);

           RegisteredDps.Add(dp.HashCode, dp);

           return dp;

       }

   }

  

DependencyObject:

 public class DependencyObject

    {

        private string _unUsedField;

        public static readonly DependencyProperty NameProperty = 

    DependencyProperty.Register("Name", typeof(string), typeof(DependencyObject), string.Empty);

        public object GetValue(DependencyProperty dp)

        {

           return DependencyProperty.RegisteredDps[dp.HashCode].Value;

       }

       public void SetValue(DependencyProperty dp, object value)

       {

           DependencyProperty.RegisteredDps[dp.HashCode].Value = value;

       }

       public string Name

       {

           get

           {

               return (string)GetValue(NameProperty);

           }

           set

           {

               SetValue(NameProperty, value);

           }

       }

   }

  

这里,首先定义了依赖属性DependencyProperty,它里面存储前面我们提到希望抽出来的字段。DP内部维护了一个全局的Map用来储存所有的DP,对外暴露了一个Register方法用来注册新的DP。当然,为了保证在Map中键值唯一,注册时需要根据传入的名字和注册类的的 HashCode取异或来生成Key。这里最关键的就是最后一个参数,设置了这个DP的默认值。

  然后定义了DependencyObject来使用DP。首先使用DependencyProperty.Register方法注册了一个新的 DP(NameProperty),然后提供了GetValue和SetValue两个方法来操作DP。最后,类似前面例子中的 NormalObject,同样定义了一个属性Name,和NormalObject的区别是,实际的值不是用字段来保存在 DependencyObject中的,而是保存在NameProperty这个DP中,通过GetValue和SetValue来完成属性的赋值取值操作。

  当然,作为一个例子,为了简洁,很多情况没有考虑,现在来测试一下是否解决了前面的问题。

  新建两个对象,NormalObject和DependencyObject,在VS下打开SOS查看:

.load sos
extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded
!DumpHeap -stat -type NormalObject
total 1 objects
Statistics:
MT    Count    TotalSize Class Name
009e30d0        1           16 DPDemonstration.NormalObject
Total 1 objects
!DumpHeap -stat -type DependencyObject
total 1 objects
Statistics:
MT    Count    TotalSize Class Name
009e31a0        1           12 DPDemonstration.DependencyObject
Total 1 objects

  这里在对象中分别建立了一个_unUsedField的字段,.Net的GC要求对象的最小Size为12字节。如果对象的Size不足12字节,则会自动补齐。默认的Object对象占用8字节,Syncblk(4字节)以及TypeHandle(4字节),为了演示方便,加入了一个 _unUsedField(4字节)来补齐。

  这里,DependencyObject相比NormalObject,减少了_name的储存空间4字节。

再进一步
万里长征第一步,这个想法可以解决我们希望的问题,这个做法还不能让人接受。在这个实现中,所有DependencyObject共用一个DP,这个可以理解,但修改一个对象的属性后,所有对象的属性相当于都被修改了,这个就太可笑了。

  所以对象属性一旦被修改,这个还是要维护在自己当中的,修改一下前面的DependencyObject,引入一个有效(Effective)的概念。

改进的DependencyObject,加入了_effectiveValues:

 public class DependencyObject

    {

        private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();

        public static readonly DependencyProperty NameProperty = 

    DependencyProperty.Register("Name", typeof(string), typeof(DependencyObject), string.Empty);

        public object GetValue(DependencyProperty dp)

        {

           EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);

           if (effectiveValue.PropertyIndex != 0)

           {

               return effectiveValue.Value;

           }

           else

           {

               return DependencyProperty.RegisteredDps[dp.HashCode].Value;

           }

       }

       public void SetValue(DependencyProperty dp, object value)

       {

           EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);

           if (effectiveValue.PropertyIndex != 0)

           {

               effectiveValue.Value = value;

           }

           else

           {

               effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index, Value = value };

               _effectiveValues.Add(effectiveValue);

           }

       }

       public string Name

       {

           get

           {

               return (string)GetValue(NameProperty);

           }

           set

           {

               SetValue(NameProperty, value);

           }

       }

   }

  

新引进的EffectiveValueEntry:

  internal struct EffectiveValueEntry

    {

        internal int PropertyIndex { get; set; }

        internal object Value { get; set; }

    }

改进的DependencyProperty,加入了ProperyIndex:

public class DependencyProperty

    {

        private static int globalIndex = 0;

        internal static Dictionary<object, DependencyProperty> RegisteredDps = new Dictionary<object, DependencyProperty>();

        internal string Name;

        internal object Value;

        internal int Index;

        internal object HashCode;

       private DependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)

       {

           this.Name = name;

           this.Value = defaultValue;

           this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();

       }

       public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)

       {

           DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultValue);

           globalIndex++;

           dp.Index = globalIndex;

           RegisteredDps.Add(dp.HashCode, dp);

           return dp;

       }

   }

  

在DependencyObject加入了一个_effectiveValues,就是把所有修改过的DP都保存在EffectiveValueEntry里,这样,就可以达到只保存修改的属性,未修改过的属性仍然读取DP的默认值,优化了属性的储存。

更进一步的发展
到目前为止,从属性到依赖属性的改造一切顺利。但随着实际的使用,又一个问题暴露出来了。使用继承,子类可以重写父类的字段,换句话说,这个默认值应该是可以子类化的。那么怎么处理,子类重新注册一个DP,传入新的默认值?

  当然,不会实现的这么丑陋。同一个DP,要想支持不同的默认值,那么内部就要维护一个对应不同DependencyObjectType的一个List,可以根据传入的DependencyObject的类型来读取它对应的默认值。

  DP内需要维护一个自描述的List,按照微软的命名规则,添加新的类型属性元数据(PropertyMetadata):

    public class PropertyMetadata

    {

        public Type Type { get; set; }

        public object Value { get; set; }

        public PropertyMetadata(object defaultValue)

        {

            this.Value = defaultValue;

        }

   }

  

对应修改DependencyProperty

  public class DependencyProperty

    {

        private static int globalIndex = 0;

        internal static Dictionary<object, DependencyProperty> RegisteredDps = new Dictionary<object, DependencyProperty>();

        internal string Name;

        internal object Value;

        internal int Index;

        internal object HashCode;

        private List<PropertyMetadata> _metadataMap = new List<PropertyMetadata>();

       private PropertyMetadata _defaultMetadata;

       private DependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)

       {

           this.Name = name;

           this.Value = defaultValue;

           this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();

           PropertyMetadata metadata = new PropertyMetadata(defaultValue) { Type = ownerType };

           _metadataMap.Add(metadata);

           _defaultMetadata = metadata;

       }

       public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)

       {

           DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultValue);

           globalIndex++;

           dp.Index = globalIndex;

           RegisteredDps.Add(dp.HashCode, dp);

           return dp;

       }

       public void OverrideMetadata(Type forType, PropertyMetadata metadata)

       {

           metadata.Type = forType;

           _metadataMap.Add(metadata);

       }

       public PropertyMetadata GetMetadata(Type type)

       {

           PropertyMetadata medatata = _metadataMap.FirstOrDefault((i) => i.Type == type) ??

               _metadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type));

           if (medatata == null)

           {

               medatata = _defaultMetadata;

           }

           return medatata;

       }

   }

  

修改DenpendencyObject中的GetValue并更改_effectiveValues,为了简洁去掉了NameProperty以及SetValue.

   public class DependencyObject

    {

       private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();

       public object GetValue(DependencyProperty dp)

       {

           EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);

           if (effectiveValue.PropertyIndex != 0)

           {

              return effectiveValue.Value;

          }

          else

          {

              PropertyMetadata metadata;

              metadata = DependencyProperty.RegisteredDps[dp.HashCode].GetMetadata(this.GetType());

              return metadata.Value;

          }

      }

   }

  这样,就可以定义一个SubDependencyObject,调用OverrideMedata向DP的_metadataMap中加入新的Metadata。

    public class SubDependencyObject : DependencyObject

    {

        static SubDependencyObject()

        {

            NameProperty.OverrideMetadata(typeof(SubDependencyObject), new PropertyMetadata("SubName"));

        }

    }

  创建一个DependencyObject以及SubDependencyObject,可以发现,Name的值已经被改为”SubName” 了。当然,实际DP中对Metadata的操作比较繁琐,当子类调用OverrideMetadata时会涉及到Merge操作,把新的Metadata 与父类的合二为一。并且在GetMetadata中,要取得自己或者是与它最近的父类的Metadata,为了可以获得最近的父类,WPF引入了一个 DependencyObjectType的类,在构造时传入BaseType=this.base.GetType(),这里为了简单,忽略不计。

WPF对依赖属性的扩展
前面的例子里,依据优化储存的思想,我们打造了一个DependencyProperty。当然,有了这样一门利器,不好好打磨打磨真是对不起它,WPF在这个基础上对DP进行了扩展,使其更加的强大。

  对通常的CLR属性来说,在Set中加入一些逻辑判断是很正常的,当然也可以在Set中发出一些事件或者更改其他一些属性。那么依赖属性,它对此又有什么支持呢?

  顺水推舟,WPF在DP的PropertyMedata中加入了PropertyChangedCallback以及 CoerceValueCallback等。这些Delegate可以在构造PropertyMetadata时传入,在SetValue过程中,会取得对应的PropertyMetadata,然后回调PropertyChangedCallback。这个PropertyMetadata可以在构建 DP时传入,也可以在子类调用OverrideMetadata时传入,这就保证了同一个DP不同的DependencyObject可以有不同的应用。 WPF对此进行了很多扩展,定义了一套属性赋值的规则,包括计算(calculate)、限制(Coerce)、验证(Validate)等等。

  当然,这些扩展说开了会很多,WPF对此也进行了精巧的设计,这也就是我们开篇提到的WPF提供了一组服务,用于扩展CLR属性。

多属性值
发展都是由需求来推动的,在WPF的实现过程中,又产生了这样一个需要:

  WPF是原生支持动画的,一个DP属性,比如Button的Width,你可以加入动画使他在1秒内由100变为200,在动画结束后,又希望它能恢复原来的属性值。同理,你可以在XAML表达式中对属性进行赋值,当表达式失效时同样期望他恢复成原来的属性值。这个需求来自于,对同一个属性的赋值可能发生在不同的场合,当对象状态改变时属性也要发生相应的变化,这里就产生了两个需要:

* 属性对外暴露一个值,但内部可以存放多个值,根据状态(条件)的改变来确定当前值。
    * 这些状态(条件)要定义优先级,根据优先级来判断当前应取哪个值。

  同一个属性有多个值,这个对CLR属性来说有些难为它了。但是对DP来说却很简单,本来DP的值就是保存在我们定义的EffectiveValueEntry中的,以前是保存一个Value,现在定义多个值就可以了。

  internal struct EffectiveValueEntry

    {

        private object _value;

        internal int PropertyIndex { get; set; }

        internal object Value 

        {

            get

           {

               return _value;

           }

           set

           {

               _value = value;

           }

       }

       internal ModifiedValue ModifiedValue 

       {

           get

           {

               if (this._value != null)

               {

                   return (this._value as ModifiedValue);

               }

               return null;

           }

       }

   }

  对应的ModifiedValue:

   internal class ModifiedValue

    {

        internal object AnimatedValue { get; set; }

        internal object BaseValue { get; set; }

        internal object CoercedValue { get; set; }

        internal object ExpressionValue { get; set; }

    }

  当属性没有被修改过,ModifiedValue为空,当修改过后,ModifiedValue被赋值。这里EffectiveValueEntry定义了很多方法如SetExpressionValue(object value), SetAnimatedValue(object value)等来向ModifiedValue中写入对应值;并且EffectiveValueEntry提供了IsAnimated,IsExpression等属性来表示当前的状态。当然,这个赋值的操作比较复杂,这个优先级分两大类:一 ModifiedValue中各属性的优先级;二对于ExpressionValue来说,它又有自己的优先级,Local>Style>Template…这里就不详细解释了。

依赖属性的优点

回过头来,总结一下依赖属性的优点:

* 优化了属性的储存,减少了不必要的内存使用。
    * 加入了属性变化通知,限制、验证等,
    * 可以储存多个值,配合Expression以及Animation等,打造出更灵活的使用方式。

windows phone之依赖属性(DependencyProperty)的更多相关文章

  1. WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性

    原文:WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性 如果你要自定义一个图片按钮控件,那么如何在主窗体绑定这个控件上图片的Source呢? ...

  2. WPF依赖属性DependencyProperty

    写在之前: 依赖属性算是WPF醉醉基础的一个组成了.平时写代码的时候,简单的绑定很轻松,但是遇到复杂的层次比较多的绑定,真的是要命.所以,我觉得深刻认识依赖属性是很有必要的.本篇只是个人学习的记录,学 ...

  3. [Silverlight]监听指定控件(FrameworkElement)的依赖属性(DependencyProperty)的更改

    前言 转载请注明出处:http://www.cnblogs.com/ainijiutian 最近在silverlight项目使用Telerik的控件,遇到一个问题.就是使用RadBusyIndicat ...

  4. WPF 依赖属性源码 洞察微软如何实现DependencyProperty

    依赖属性DependencyProperty是wpf最重要的一个类,理解该类如何实现对学习wpf帮助很大! 终于找到了该类的源码!仔细阅读源码,看看微软如何玩的花招! File: Base\Syste ...

  5. [转]WPF的依赖属性是怎么节约内存的

    WPF升级了CLR的属性系统,加入了依赖属性和附加属性.依赖属性的使用有很多好处,其中有两点是我认为最为亮眼的: 1)节省内存的开销; 2)属性值可以通过Binding依赖于其它对象上,这就使得我的数 ...

  6. 重新想象 Windows 8 Store Apps (16) - 控件基础: 依赖属性, 附加属性, 控件的继承关系, 路由事件和命中测试

    原文:重新想象 Windows 8 Store Apps (16) - 控件基础: 依赖属性, 附加属性, 控件的继承关系, 路由事件和命中测试 [源码下载] 重新想象 Windows 8 Store ...

  7. 背水一战 Windows 10 (78) - 自定义控件: 基础知识, 依赖属性, 附加属性

    [源码下载] 背水一战 Windows 10 (78) - 自定义控件: 基础知识, 依赖属性, 附加属性 作者:webabcd 介绍背水一战 Windows 10 之 控件(自定义控件) 自定义控件 ...

  8. 背水一战 Windows 10 (67) - 控件(控件基类): DependencyObject - CoreDispatcher, 依赖属性的设置与获取, 依赖属性的变化回调

    [源码下载] 背水一战 Windows 10 (67) - 控件(控件基类): DependencyObject - CoreDispatcher, 依赖属性的设置与获取, 依赖属性的变化回调 作者: ...

  9. 【转】【译】在 Windows 10 应用程序中注册任意依赖属性的改变

    原文地址:http://visuallylocated.com/post/2015/04/01/Registering-to-any-DependencyProperty-changing-in-Wi ...

随机推荐

  1. 蓝桥杯 算法训练 ALGO-50 数组查找及替换

    算法训练 数组查找及替换   时间限制:1.0s   内存限制:512.0MB 问题描述 给定某整数数组和某一整数b.要求删除数组中可以被b整除的所有元素,同时将该数组各元素按从小到大排序.如果数组元 ...

  2. java代码-----逻辑运算符

    总结:运算符不熟悉, package com.aa; public class Ss { public static void main(String[] args) { int i=1,j=10; ...

  3. 双杀 0day 漏洞(CVE-2018-8174)复现

    漏洞描述: CVE-2018-8174 是 Windows VBScript Engine 代码执行漏洞. 微软在4月20日早上确认此漏洞,并于5月8号发布了官方安全补丁,对该 0day 漏洞进行了修 ...

  4. 程序实现DataGrid过滤设置

    给gdv_pro.ActiveFilterString赋值就能直接控制DataGrid过滤 gdv_pro.ActiveFilterString语法: 最简单方式:gdv_pro.ActiveFilt ...

  5. 最长上升子序列(LIS)

    最长递增子序列,Longest Increasing Subsequence 下面我们简记为 LIS.排序+LCS算法 以及 DP算法就忽略了,这两个太容易理解了. 假设存在一个序列d[1..9] = ...

  6. 使用axis2进行WebService的开发

    使用axis2进行WebService的开发 Apache Axis2 是 Apache Axis SOAP 项目的后继项目.此项目是 Web 服务核心引擎的重要改进,目标是成为 Web 服务和面向服 ...

  7. 在C#中动态调用webService

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.W ...

  8. Oracle 环境下 GoldenGate 集成抽取(Integrated Capture)模式与传统抽取模式(Classic Capture)间的切换

    检查抽取进程模式 在 GGSCI 环境下,执行类似如下语句查看特定进程的状态. GGSCI> info <Group_Name> 其中,<Group_Name> 为进程名 ...

  9. 深入剖析SolrCloud(一)

    作者:洞庭散人 出处:http://phinecos.cnblogs.com/ 本博客遵从Creative Commons Attribution 3.0 License,若用于非商业目的,您可以自由 ...

  10. laravel中的模型关联之(一对一)

    一对一 一对一的关联关系就相当于,赞和文章之间的关系,一个用户只能赞一次一个文章,只能和文章发生一次关系, 用户和赞的文章是唯一的一份,这里举得是判断用户是否赞过某个文章 一个参数都是你要获取的模型, ...