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匹配。
因此依赖属性的性能由属性表的检索性能决定。不能说使用默认值比使用用户值快,但一个实例里,用户设定值太多肯定影响依赖属性访问速度。
WPF学习笔记二 依赖属性实现原理及性能分析的更多相关文章
- WPF学习笔记一 依赖属性及其数据绑定
本文想通过由浅入深的讲解让读者比较深的理解依赖属性. 首先,我们回顾一下依赖属性的发展历史. 最初,人们提出面向对象编程时,并没有属性这个说法,当时叫做成员变量.一个对象由成员变量和成员函数组成,如 ...
- WPF 学习笔记-设置属性使窗口不可改变大小
原文:WPF 学习笔记-设置属性使窗口不可改变大小 调整Windows下的ResizeMode属性: ResizeMode = NoResize Resize属性是控制Windows是否可以改变大小, ...
- WPF学习笔记二之依赖属性
1.快捷生成依赖属性:propdp然后按两次tab键 2.应用场景:自定义控件 什么是依赖属性:依赖属性自己没有值,通过依赖别人(如Binding)来获得值. 依赖属性为什么会出现:控件常用字段有限, ...
- HTML5学习笔记<二>:元素,属性,格式化
HTML元素 元素是指从开始标签到结束标签的所有代码. 开始(开放)标签 元素内容 结束(闭合)标签 <p> this is my web page </p> 没有内容的 HT ...
- Java集合类学习笔记(各种Map实现类的性能分析)
HashMap和Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的.线程安全的集合,因此HashMap通常比Hashtable要快. TreeMap比HashMap和Hasht ...
- WPF的Binding学习笔记(二)
原文: http://www.cnblogs.com/pasoraku/archive/2012/10/25/2738428.htmlWPF的Binding学习笔记(二) 上次学了点点Binding的 ...
- qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)
原博主博客地址:http://blog.csdn.net/qq21497936本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78516 ...
- Spring学习笔记之依赖的注解(2)
Spring学习笔记之依赖的注解(2) 1.0 注解,不能单独存在,是Java中的一种类型 1.1 写注解 1.2 注解反射 2.0 spring的注解 spring的 @Controller@Com ...
- amazeui学习笔记二(进阶开发3)--HTML/CSS规范Rules
amazeui学习笔记二(进阶开发3)--HTML/CSS规范Rules 一.总结 1.am:以 am 为命名空间 2.模块状态: {命名空间}-{模块名}-{状态描述} 3.子模块: {命名空间}- ...
随机推荐
- webdriver xpath
aa=wd.find_elements_by_xpath('//a') for a in aa: print(a.text) #显示所有A标签中文本 aa=wd.find_elements_by_xp ...
- 给potplayer配置iptv源,看所有你想看的电视
目录 一.展示: 二.下载 三.播放 一.展示: 二.下载 Github 上的开源项目:iptv-org/iptv 传送门: https://github.com/iptv-org/iptv 该项目包 ...
- dataX windows10安装
按照视频课程,从Github上下载文件:https://github.com/alibaba/DataX 然后将下载的压缩包解压即可,不过需要的前提Python环境是要求python2,于是在pyth ...
- POJ 博弈论
poj1704 Georgia and Bob 题目链接:http://poj.org/problem?id=1704 题意:如图所示,两个人在玩一个游戏,排成直线的格子上有n个棋子,两人依次将棋子向 ...
- git的一些常用基础命令
一些常用的git命令操作简单总结 记录常用的git命令,附带命令的简单使用说明 git区域分布 remote远程仓库 repository本地仓库 index暂存区 workspace工作区 stas ...
- 【对线面试官】Kafka基础入门
<对线面试官>系列目前已经连载33篇啦,这是一个讲人话面试系列 [对线面试官]Java注解 [对线面试官]Java泛型 [对线面试官] Java NIO [对线面试官]Java反射 &am ...
- ajax()返回Array
后台查询的数据为数组$arr,需要将数组 echo json_encode($arr);前台ajax拿到数据 然后用 eval("(+data+)"); 来将json转为json对 ...
- 原来select语句在MySQL中是这样执行的!看完又涨见识了!这回我要碾压面试官!
大家好,我是冰河~~ MySQL作为互联网行业使用最多的关系型数据库之一,与其免费.开源的特性是密不可分的.然而,很多小伙伴工作了很多年,只知道使用MySQL进行CRUD操作,这也导致很多小伙伴工作多 ...
- ffmpeg 任意文件读取漏洞/SSRF漏洞 (CVE-2016-1897/CVE-2016-1898)
影响版本 在FFMpeg2.X poc http://192.168.49.2:8000/?name={%25%20for%20c%20in%20[].__class__.__base__.__sub ...
- netty系列之:netty中的Channel详解
目录 简介 Channel详解 异步IO和ChannelFuture Channel的层级结构 释放资源 事件处理 总结 简介 Channel是连接ByteBuf和Event的桥梁,netty中的Ch ...