在这里讨论依赖属性实现原理,目的只是学习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匹配。

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

WPF学习笔记二 依赖属性实现原理及性能分析的更多相关文章

  1. WPF学习笔记一 依赖属性及其数据绑定

    本文想通过由浅入深的讲解让读者比较深的理解依赖属性.  首先,我们回顾一下依赖属性的发展历史. 最初,人们提出面向对象编程时,并没有属性这个说法,当时叫做成员变量.一个对象由成员变量和成员函数组成,如 ...

  2. WPF 学习笔记-设置属性使窗口不可改变大小

    原文:WPF 学习笔记-设置属性使窗口不可改变大小 调整Windows下的ResizeMode属性: ResizeMode = NoResize Resize属性是控制Windows是否可以改变大小, ...

  3. WPF学习笔记二之依赖属性

    1.快捷生成依赖属性:propdp然后按两次tab键 2.应用场景:自定义控件 什么是依赖属性:依赖属性自己没有值,通过依赖别人(如Binding)来获得值. 依赖属性为什么会出现:控件常用字段有限, ...

  4. HTML5学习笔记<二>:元素,属性,格式化

    HTML元素 元素是指从开始标签到结束标签的所有代码. 开始(开放)标签 元素内容 结束(闭合)标签 <p> this is my web page </p> 没有内容的 HT ...

  5. Java集合类学习笔记(各种Map实现类的性能分析)

    HashMap和Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的.线程安全的集合,因此HashMap通常比Hashtable要快. TreeMap比HashMap和Hasht ...

  6. WPF的Binding学习笔记(二)

    原文: http://www.cnblogs.com/pasoraku/archive/2012/10/25/2738428.htmlWPF的Binding学习笔记(二) 上次学了点点Binding的 ...

  7. qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

    原博主博客地址:http://blog.csdn.net/qq21497936本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78516 ...

  8. Spring学习笔记之依赖的注解(2)

    Spring学习笔记之依赖的注解(2) 1.0 注解,不能单独存在,是Java中的一种类型 1.1 写注解 1.2 注解反射 2.0 spring的注解 spring的 @Controller@Com ...

  9. amazeui学习笔记二(进阶开发3)--HTML/CSS规范Rules

    amazeui学习笔记二(进阶开发3)--HTML/CSS规范Rules 一.总结 1.am:以 am 为命名空间 2.模块状态: {命名空间}-{模块名}-{状态描述} 3.子模块: {命名空间}- ...

随机推荐

  1. C语言:带参数的宏与函数的区别

    带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算:宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存.而函数是一段可以重复使用的代码,会被编译,会给它 ...

  2. python 遍历字典中的键和值

    #遍历字典中的所有键和值 zd1={"姓名":"张三","年龄":20,"性别":"女"} zd2= ...

  3. 【技巧】使用PPT更换背景色

    主要记述使用PPT来更换图片某一部分的背景色 把想要更改的图片粘贴到PPT里. 依次选择[格式][颜色][设置透明色],然后点击需要更改背景的地方 将自己的目标颜色复制一下,填充上去,选择[置于底层]

  4. selenium3 + python - js&jquery操作处理

    # 推荐学习:https://www.w3school.com.cn/js/index.asp## 下面以简书登录&注册定位元素为例"""js定位 id name ...

  5. 二本,拿腾讯,阿里 offer 了

    我的春招 Hello,首先自我介绍一下,我是一所普普通通的二本院校的大三学生,坐标江苏. 今年三月份拿到了腾讯实习的offer,人生中第一次面试是腾讯,部门是 TEG 的云架构,并且顺利签约,说实话内 ...

  6. 【搜索】棋盘 luogu-3956

    分析 按照这个题目随便写一个搜索就可以了 AC代码 #include <cstdio> #include <cstring> #include <algorithm> ...

  7. 【字符串+排序】宇宙总统 luogu-1781

    题目描述 地球历公元6036年,全宇宙准备竞选一个最贤能的人当总统,共有n个非凡拔尖的人竞选总统,现在票数已经统计完毕,请你算出谁能够当上总统. 分析 给字符串排个序. AC代码 #include & ...

  8. js 跨域请求失败

    注:错误返回:Failed to load http://xxxxxxxxxxx: No 'Access-Control-Allow-Origin' header is present on the ...

  9. Resnet网络详细结构(针对Cifar10)

    Resnet网络详细结构(针对Cifar10) 结构 具体结构(Pytorch) conv1 (conv1): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, ...

  10. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件

    精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件 内容简介:本文介绍 Spring Boot 的配置文件和配置管理,以及介绍了三种读取配置文 ...