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.子模块: {命名空间}- ...
随机推荐
- C语言:GB2312编码和GBK编码,将中文存储到计算机
计算机是一种改变世界的发明,很快就从美国传到了全球各地,得到了所有国家的认可,成为了一种不可替代的工具.计算机在广泛流行的过程中遇到的一个棘手问题就是字符编码,计算机是美国人发明的,它使用的是 ASC ...
- 家庭账本开发day08
对查询到额数据进行相关的操作,删除.对删除按钮绑定事件 点击后发送ajax请求到servlet,删除相关的数据后,返回flag到前端 若后台删除成功,则前台进行相应的.close():输出点击行的数据 ...
- 答读者问(1):非模式物种找marker;如何根据marker定义细胞类型
下午花了两个小时回答读者的疑问,觉得可以记录下来,也许能帮到一部分人. 第一位读者做的是非模式物种的单细胞. 一开始以为是想问我非模式物种的marker基因在哪儿找,读者朋友也提到了blast 研究的 ...
- mysql实现主从复制、读写分离的配置方法(二)
由于接触主从复制,读写分离的时间比较短,应用还不够熟练,目的是能通过MyCat实现基础的读写分离操作. 其核心功能是分库分表,配合数据库的主从模式还可实现读写分离. 1. 测试环境 一台win10主机 ...
- 使用bind部署DNS主从服务器
说明:这里是Linux服务综合搭建文章的一部分,本文可以作为单独搭建主从DNS服务器的参考. 注意:这里所有的标题都是根据主要的文章(Linux基础服务搭建综合)的顺序来做的. 如果需要查看相关软件版 ...
- Linux if[......] then ......else...... fi
条件表达式 if [ -f file ] 如果文件存在if [ -d ... ] 如果目录存在if [ -s file ] 如果文件存在且非空 if [ -r file ] ...
- 最大公约数and最小公倍数(Java版)
1.最大公约数and最小公倍数 import java.util.Scanner; public class MultipleAndDivisor { public static void main( ...
- js--class类、super和estends关键词的学习笔记
前言 JavaScript 语言在ES6中引入了 class 这一个关键字,在学习面试的中,经常会遇到面试官问到谈一下你对 ES6 中class的认识,同时我们的代码中如何去使用这个关键字,使用这个关 ...
- springboot自定义ObjectMapper序列化、配置序列化对LocalDateTime的支持
背景 问题1:项目中使用默认自带的jackson进行前后端交互,实现数据对象的序列化和反序列化,默认的ObjectMapper采用小驼峰的格式,但是调用其他业务的http接口时,ObjectMappe ...
- C++面向对象 1(类-封装)
1 //类和对象 2 //C++ 面向对象 三大特性 : 封装 继承 多态 3 4 //设计一个圆类,求圆的周长 5 //圆周长 = 2*PI * 半径 6 7 #include <iostre ...