zoukankan      html  css  js  c++  java
  • 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匹配。

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

  • 相关阅读:
    ASP.NET CORE 使用Consul实现服务治理与健康检查(2)——源码篇
    ASP.NET CORE 使用Consul实现服务治理与健康检查(1)——概念篇
    Asp.Net Core 单元测试正确姿势
    如何通过 Docker 部署 Logstash 同步 Mysql 数据库数据到 ElasticSearch
    Asp.Net Core2.2 源码阅读系列——控制台日志源码解析
    使用VS Code 开发.NET CORE 程序指南
    .NetCore下ES查询驱动 PlainElastic .Net 升级官方驱动 Elasticsearch .Net
    重新认识 async/await 语法糖
    EF添加
    EF修改部分字段
  • 原文地址:https://www.cnblogs.com/bruce1992/p/14196802.html
Copyright © 2011-2022 走看看