zoukankan      html  css  js  c++  java
  • 依耐项属性- 在需要使用的情况下添加

    这段是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.    }
    复制代码

    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.    }
    复制代码

    这里,首先定义了依赖属性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:WINDOWSMicrosoft.NETFrameworkv2.0.50727sos.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:

    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,加入了ProperyIndex:

    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等,打造出更灵活的使用方式

  • 相关阅读:
    测试
    mysql数据库 select语句全集
    Markdown文本的书写格式详解--有道云笔记
    mysql数据忘记库密码
    最新版mysql基本命令操作
    Python从入门到放弃
    第二阶段冲刺
    周总结15
    找水王
    用户体验评价
  • 原文地址:https://www.cnblogs.com/zhuqun/p/14803478.html
Copyright © 2011-2022 走看看