• WPF学习笔记二 依赖属性实现原理及性能分析


     在这里讨论依赖属性实现原理,目的只是学习WPF是怎么设计依赖属性的,同时更好的使用依赖属性。

      首先我们来思考一个简单的问题:我们希望能验证属性的值是否有效,属性变更时进行自己的处理。回顾一下.net的处理方式

    复制代码
    Public Class MyClass{
    private int index;
    Public int Index{
    get{
    return index;
    }
    set{
    if(属性变更时){
    //有效性检查
    //处理或激发事件通知外部处理
    }
    }
    }
    }
    复制代码

    现在,我们希望设计一套属性系统,能验证属性的值是否有效,属性变更时能进行处理(WPF属性系统肯定不是为这个设计的,但它支持这种功能)。我希望读者在这里思考一下,你会怎么做,最后看WPF怎么做。

       我先提出第一种设计:设计一个基类,用来实现以上需求。当你定义一个属性,希望该属性能验证属性的值是否有效,属性变更时能进行处理时,让该属性从这个基类继承,就可以达到目的了。基类定义如下:

    复制代码
    Public Class PropertyBase {
      protected bool virtual IsValidValue(object value){
        return true;
      }
      protected void virtualValueChangedHandler(Object sender, PropertyChangedEventArgs e){

      }

    }
    复制代码

    但显然,WPF不会这么做。倒不是这种方法实现不了WPF属性系统的功能,而是这样做对WPF开发者来说真的是灾难。想想如果你定义一个简单的double型的依赖属性FontSize,却要去写一个类。从系统性能,内存乱费来说也是不能接受的。既然不能采用这种继承方式,那就定义一个类,所有依赖属性均声明为这个类的对象,让这个类来完成以上功能。WPF中这个类的名字叫DependencyProperty,依赖属性的声明如下:

    public static readonly DependencyProperty FontSizeProperty;

    我们知道.net属性一般有访问器,WPF也不例外,上面代码完善一下:

    复制代码
    public class Control {
      public static readonly DependencyProperty FontSizeProperty;
      publicdouble FontSize{
        get{...};
        set{...};
       }
    }
    复制代码

      从内部来说,FontSizeProperty才是真正的依赖属性,FontSize只是外部访问FontSizeProperty的接口。很显然,上面的get/set必须和FontSizeProperty关联,所以WPF加入了一对访问函数SetValue/GetValue.至于怎么关联,那是SetValue/GetValue的实现问题。由于每一个依赖属性的访问要通过SetValue/GetValue,因此WPF定义了一个DependencyObject,来实现SetValue/GetValue,进一步完善以上代码:

    复制代码
    public class Control :DependencyObject{
      public static readonly DependencyProperty FontSizeProperty;
      public double FontSize{
     get {
         return (double)GetValue(FontSizeProperty);
      }
         set {
        SetValue(FontSizeProperty, value);
       }
      }
    }
    复制代码

    还有一个问题,FontSizeProperty为什么定义为public static readonly ?
    定义为public 是有原因的,WPF有一种特殊属性,叫附加属性,需要直接访问FontSizeProperty的方法才能实现,所以FontSizeProperty是public 的。至于static,和依赖属性的实现有关,也就是说,一个类,不管同一个依赖属性有多少个实例,均对应同一个DependencyProperty 。比如你创建100个Control ,每个Control 都有一个FontSize属性,但这100个FontSize均对应同一个FontSizeProperty实例。

    接下来想知道的是:DependencyProperty怎么实现?
    我们知道一个依赖属性可以是简单类型(如bool,int),也可以是复杂类型(如List,自定义类型),大家一定想到了一个东西,那就是泛类型技术,.net中就大量使用了泛类型技术,我们使用泛类型来定义依赖属性:

    public class DependencyProperty<T> {
    }

    但事实上WPF的DependencyProperty不是泛类型!为什么?
    原因很简单,WPF属性系统想知道的不仅仅依赖属性的类型,还有依赖属性名,所有者的类型,元数据,回调代理等,泛类型并不能解决这些问题,所以WPF使用了一个Register()函数,由该函数将所有信息提供给WPF属性系统。就这样,我们完成了定义一个依赖属性的完整定义。

    复制代码
    public class Control :DependencyObject{
      public static readonly DependencyProperty FontSizeProperty;
      public double FontSize{
     get {
         return (double)GetValue(FontSizeProperty);
      }
         set {
        SetValue(FontSizeProperty, value);
       }
      }

       static Control () {

           FontSizeProperty= DependencyProperty.Register(
                    "FontSize", typeof(double), typeof(Control),
                    new FrameworkPropertyMetadata(),null));   
            }
    }

    复制代码

      这里也有人会问了:为什么使用Register()函数来传递数据,而不用构造函数来传递?如果在创建一个依赖属性时忘了调用Register()怎么办?此问题由于涉及到DependencyProperty的具体实现,稍后再说。

      上面提到,100个Control实例会有100个FontSize,均对应同一个FontSizeProperty实例。读者一定会想,哦,那DependencyProperty中一定有一张表,来保存每个FontSize的值。开始我也这么想,但事实上不太一样。不过,DependencyProperty中确实有一张表,并且还是静态的!! 

    public sealed class DependencyProperty {    
      //全局的IDictionary用来储存所有的DependencyProperty
    internal static IDictionary<int, DependencyProperty> properties =
              new Dictionary<int, DependencyProperty>();

     那这张表里保存什么呢?就让Register()函数来回答吧,这是创建DependencyProperty的入口。下面代码不全,但已能说明问题。  

    复制代码
    public sealed class DependencyProperty {
    //全局的IDictionary用来储存所有的DependencyProperty
    internal static IDictionary<int, DependencyProperty> properties = new Dictionary<int, DependencyProperty>();
    private static int globalIndex = 0;
    private int _index;

    //构造函数私有,保证外界不会对它进行实例化
    private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata) {
    ...
    }
    public int Index {
    get{
         return_index;
       }
       set{
        _index =value;
       }
     }
    //注册的公用方法,把这个依赖属性加入到IDictionary的键值集合中,GetHashCode为name和owner_type的GetHashCode取异,Value就是我们注册的DependencyProperty
    public static DependencyProperty Register(stringname, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata) {
      DependencyProperty property = new DependencyProperty(name, propertyType, ownerType, defaultMetadata);
    globalIndex++;
    property.Index =globalIndex;
    if(properties.ContainsKey(property.GetHashCode())) {
       throw new InvalidOperationException("A property with the same name already exists");
    }
    //把刚实例化的DependencyProperty添加到这个全局的IDictionary
       properties.Add(property.GetHashCode(), property);
       returnproperty;
     }
    }
    复制代码

      由此可见,DependencyProperty的properties中保存的是所有依赖属性创建时的数据,也就是为什么WPF每个依赖属性都可以恢复到默认值,即使你改变了该依赖属性的值很多次。
      这段代码也解释了另外一个问题:为什么使用Register()函数来传递数据,而不用构造函数来传递。因为DependencyProperty的构造函数是私有的。当然你也可以和WPF不一样,去掉Register()函数,把构造函数改为Public,并把Register()中的其他功能移到构造函数中来。至于其中利弊你自己去衡量吧。 

      DependencyProperty的属性当然不止上面代码中的几个,我特意保留了一个Index,因为Index将关系到依赖属性值的真正访问。分析上面代码,得出结论:Index的值是和每一个依赖属性一一对应的,不管你现在开发的系统有多少个类,每个类有多少个依赖属性。同时,一个依赖属性不管有多少个实例,都只有一个Index值。上面提到的100个FontSize对应的也是同一个Index。

      用户设定的依赖属性值到底保存在哪里?别忘了SetValue/GetValue,它们是DependencyObject的方法。到这里读者大概想到了用户设定的依赖属性值到底保存在哪里。没错,就在DependencyObject的_effectiveValues中。

    public abstract class DependencyObject :  IDisposable{
      private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();
      public object GetValue(DependencyProperty dp){...}
      public void SetValue(DependencyProperty dp, objectvalue){...}

      由于DependencyObject是依赖属性拥有者的基类,因此,每创建一个实例,就会创建一个List<EffectiveValueEntry>,以List的方式保存该实例的用户设定的依赖属性值。
    绕了一圈,从终点又回到原点,WPF中属性的用户值和.net中一样,都保存在该实例中。只不过.net区分不了用户值和默认值,只有当前值,而WPF把默认值保存到了DependencyProperty中。

      留一个问题给读者思考:依赖属性FontSize对应一个DependencyProperty的Index值,是FontSize在DependencyObject.List<EffectiveValueEntry>中的位置索引吗?

    关于依赖属性的性能问题,就简单说一下:

      1.所有依赖属性的默认值保存在DependencyProperty的属性表中,读取(不写)时通过属性的HashCode检索

          2.每个实例也有一张属性表,保存该实例当前依赖属性的用户值,通过DependencyProperty的Index匹配。

    因此依赖属性的性能由属性表的检索性能决定。不能说使用默认值比使用用户值快,但一个实例里,用户设定值太多肯定影响依赖属性访问速度。

  • 相关阅读:
    .net 关于路径的总结
    asp.net Base64加解密
    asp.net中的<% %>,<%= %>,<%# %><%$ %>的使用
    asp.net Swiper 轮播动画
    ASP.NET中Literal控件的使用方法(用于向网页中动态添加内容)
    asp.net 获取表单中控件的值
    rgb值转换成16进制
    关于background全解
    移动端的性能陷阱
    原生JS实现雪花特效
  • 原文地址:https://www.cnblogs.com/bruce1992/p/14196802.html
Copyright © 2020-2023  润新知