zoukankan      html  css  js  c++  java
  • (转)从属性到依赖属性

    2009-09-10 17:16 by 周永恒

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

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

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

    从属性说起

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

       1: public class NormalObject
       2: {
       3:     private string _unUsedField;
       4:  
       5:     private string _name;
       6:     public string Name
       7:     {
       8:         get
       9:         {
      10:             return _name;
      11:         }
      12:         set
      13:         {
      14:             _name = value;
      15:         }
      16:     }   
      17: }

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

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

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

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

    依赖属性的原型

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

    DependencyProperty:

       1: public class DependencyProperty
       2: {
       3:     internal static Dictionary<object, DependencyProperty> RegisteredDps = new Dictionary<object, DependencyProperty>();
       4:     internal string Name;
       5:     internal object Value;
       6:     internal object HashCode;
       7:  
       8:     private DependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)
       9:     {
      10:         this.Name = name;
      11:         this.Value = defaultValue;
      12:         this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
      13:     }
      14:  
      15:     public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)
      16:     {
      17:         DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultValue);
      18:         RegisteredDps.Add(dp.HashCode, dp);
      19:         return dp;
      20:     }
      21: }
      22:  

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

    DependencyObject:

       1: public class DependencyObject
       2: {
       3:     private string _unUsedField;
       4:  
       5:     public static readonly DependencyProperty NameProperty = 
       6: DependencyProperty.Register("Name", typeof(string), typeof(DependencyObject), string.Empty);
       7:  
       8:     public object GetValue(DependencyProperty dp)
       9:     {
      10:         return DependencyProperty.RegisteredDps[dp.HashCode].Value;
      11:     }
      12:  
      13:     public void SetValue(DependencyProperty dp, object value)
      14:     {
      15:         DependencyProperty.RegisteredDps[dp.HashCode].Value = value;
      16:     }
      17:  
      18:     public string Name
      19:     {
      20:         get
      21:         {
      22:             return (string)GetValue(NameProperty);
      23:         }
      24:         set
      25:         {
      26:             SetValue(NameProperty, value);
      27:         }
      28:     }
      29: }
      30:  

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

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

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

    .load sosextension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded!DumpHeap -stat -type NormalObjecttotal 1 objectsStatistics:MT    Count    TotalSize Class Name009e30d0        1           16 DPDemonstration.NormalObjectTotal 1 objects!DumpHeap -stat -type DependencyObjecttotal 1 objectsStatistics:MT    Count    TotalSize Class Name009e31a0        1           12 DPDemonstration.DependencyObjectTotal 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:

       1: public class DependencyObject
       2: {
       3:     private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();
       4:  
       5:     public static readonly DependencyProperty NameProperty = 
       6: DependencyProperty.Register("Name", typeof(string), typeof(DependencyObject), string.Empty);
       7:  
       8:     public object GetValue(DependencyProperty dp)
       9:     {
      10:         EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
      11:         if (effectiveValue.PropertyIndex != 0)
      12:         {
      13:             return effectiveValue.Value;
      14:         }
      15:         else
      16:         {
      17:             return DependencyProperty.RegisteredDps[dp.HashCode].Value;
      18:         }
      19:     }
      20:  
      21:     public void SetValue(DependencyProperty dp, object value)
      22:     {
      23:         EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
      24:         if (effectiveValue.PropertyIndex != 0)
      25:         {
      26:             effectiveValue.Value = value;
      27:         }
      28:         else
      29:         {
      30:             effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index, Value = value };
      31:             _effectiveValues.Add(effectiveValue);
      32:         }
      33:     }
      34:  
      35:     public string Name
      36:     {
      37:         get
      38:         {
      39:             return (string)GetValue(NameProperty);
      40:         }
      41:         set
      42:         {
      43:             SetValue(NameProperty, value);
      44:         }
      45:     }
      46: }

    新引进的EffectiveValueEntry:

       1: internal struct EffectiveValueEntry
       2: {
       3:     internal int PropertyIndex { get; set; }
       4:  
       5:     internal object Value { get; set; }
       6: }

     改进的DependencyProperty,加入了PropertyIndex:

       1: public class DependencyProperty
       2: {
       3:     private static int globalIndex = 0;
       4:     internal static Dictionary<object, DependencyProperty> RegisteredDps = new Dictionary<object, DependencyProperty>();
       5:     internal string Name;
       6:     internal object Value;
       7:     internal int Index;
       8:     internal object HashCode;
       9:  
      10:     private DependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)
      11:     {
      12:         this.Name = name;
      13:         this.Value = defaultValue;
      14:         this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
      15:     }
      16:  
      17:     public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)
      18:     {
      19:         DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultValue);
      20:         globalIndex++;
      21:         dp.Index = globalIndex;
      22:         RegisteredDps.Add(dp.HashCode, dp);
      23:         return dp;
      24:     }
      25: }

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

    更进一步的发展

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

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

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

       1: public class PropertyMetadata
       2: {
       3:     public Type type { get; set; }
       4:     public object Value { get; set; }
       5:  
       6:     public PropertyMetadata(object defaultValue)
       7:     {
       8:         this.Value = defaultValue;
       9:     }
      10: }

    对应修改DependencyProperty

       1: public class DependencyProperty
       2: {
       3:     private static int globalIndex = 0;
       4:     internal static Dictionary<object, DependencyProperty> RegisteredDps = new Dictionary<object, DependencyProperty>();
       5:     internal string Name;
       6:     internal object Value;
       7:     internal int Index;
       8:     internal object HashCode;
       9:     private List<PropertyMetadata> _metadataMap = new List<PropertyMetadata>();
      10:     private PropertyMetadata _defaultMetadata;
      11:  
      12:     private DependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)
      13:     {
      14:         this.Name = name;
      15:         this.Value = defaultValue;
      16:         this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
      17:  
      18:         PropertyMetadata metadata = new PropertyMetadata(defaultValue) { Type = ownerType };
      19:         _metadataMap.Add(metadata);
      20:         _defaultMetadata = metadata;
      21:     }
      22:  
      23:     public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)
      24:     {
      25:         DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultValue);
      26:         globalIndex++;
      27:         dp.Index = globalIndex;
      28:         RegisteredDps.Add(dp.HashCode, dp);
      29:         return dp;
      30:     }
      31:  
      32:     public void OverrideMetadata(Type forType, PropertyMetadata metadata)
      33:     {
      34:         metadata.Type = forType;
      35:         _metadataMap.Add(metadata);
      36:     }
      37:  
      38:     public PropertyMetadata GetMetadata(Type type)
      39:     {
      40:         PropertyMetadata medatata = _metadataMap.FirstOrDefault((i) => i.Type == type) ??
      41:             _metadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type));
      42:         if (medatata == null)
      43:         {
      44:             medatata = _defaultMetadata;
      45:         }
      46:         return medatata;
      47:     }
      48: }

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

       1: public class DependencyObject
       2: {
       3:    private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();
       4:  
       5:    public object GetValue(DependencyProperty dp)
       6:    {
       7:        EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
       8:        if (effectiveValue.PropertyIndex != 0)
       9:        {
      10:            return effectiveValue.Value;
      11:        }
      12:        else
      13:        {
      14:            PropertyMetadata metadata;
      15:            metadata = DependencyProperty.RegisteredDps[dp.HashCode].GetMetadata(this.GetType());
      16:            return metadata.Value;
      17:        }
      18:    }
      19: }

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

       1: public class SubDependencyObject : DependencyObject
       2: {
       3:     static SubDependencyObject()
       4:     {
       5:         NameProperty.OverrideMetadata(typeof(SubDependencyObject), new PropertyMetadata("SubName"));
       6:     }
       7: }

      创建一个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,现在定义多个值就可以了。

       1: internal struct EffectiveValueEntry
       2: {
       3:     private object _value;
       4:  
       5:     internal int PropertyIndex { get; set; }
       6:  
       7:     internal object Value 
       8:     {
       9:         get
      10:         {
      11:             return _value;
      12:         }
      13:         set
      14:         {
      15:             _value = value;
      16:         }
      17:     }
      18:  
      19:     internal ModifiedValue ModifiedValue 
      20:     {
      21:         get
      22:         {
      23:             if (this._value != null)
      24:             {
      25:                 return (this._value as ModifiedValue);
      26:             }
      27:             return null;
      28:         }
      29:     }
      30: }

    对应的ModifiedValue:

       1: internal class ModifiedValue
       2: {
       3:     internal object AnimatedValue { get; set; }
       4:     internal object BaseValue { get; set; }
       5:     internal object CoercedValue { get; set; }
       6:     internal object ExpressionValue { get; set; }
       7: }

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

    依赖属性的优点

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

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

    总结

      借助于依赖属性,WPF提供了强大的属性系统,可以支持数据绑定、样式、动画、附加属性等功能。这篇文章主要是简略的实现了一个从属性到依赖属性的发展过程,当然,具体和WPF的实现还有偏差,希望朋友们都能抓住这个主要的脉络,更好的去玩转它。

      除了依赖属性的实现,还有一些很重要的部分,比如借助于依赖属性提出的附加属性,以及如何利用依赖属性来更好的设计实现程序,使用依赖属性有哪些要注意的地方。呵呵,那就,下篇吧。

    示例程序下载

    作者:周永恒 
    出处:http://www.cnblogs.com/Zhouyongh  
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利

  • 相关阅读:
    不可小视视图对效率的影响力
    Maximum Margin Planning
    PhysicsBased Boiling Simulation

    Learning Behavior Styles with Inverse Reinforcement Learning
    Simulating Biped Behaviors from Human Motion Data
    Nearoptimal Character Animation with Continuous Control
    Apprenticeship Learning via Inverse Reinforcement Learning
    回报函数学习的学徒学习综述
    Enabling Realtime Physics Simulation in Future Interactive Entertainment
  • 原文地址:https://www.cnblogs.com/victorruan/p/2442329.html
Copyright © 2011-2022 走看看