wpf-依赖属性
转自:
http://blog.csdn.net/na_onlyone/article/details/53308243
前言:
在使用WPF的时候,总会有一个疑问,依赖属性跟普通的类属性有什么区别,微软要在WPF引入它想要解决什么问题?如果不解除这个疑惑,在编程的时候心里总会不踏实。因此我在网上找了一些资料,终于弄懂了它的由来和机制,特意和大家分享,如有不足,请各位指正!
依赖属性的由来:
在WinForm时代,每个控件类(如TextBox)都会包含许多属性,但是真正用到的少之又少(如text),其他属性就会白白耗费内存资源。那么问题来了,如果只生成一个控件对象,“无用”的属性对性能影响不大,但是当你在一个窗体实例化几十个控件对象的时候,那内存消耗就很可观了。所以,我们应该引入一个机制,在实例化对象的时候,按需“给”它属性,这样子就不会让“无用”的属性白白占用内存资源,因此依赖属性诞生了。
依赖属性的定义:
先比较浅显地讲讲依赖属性的定义,即本身可以没有值,而依赖于其他数据源而取得值的属性。
自定义依赖属性:
在深入了解依赖属性之前,很有必要弄清楚依赖属性是如何敲出来的。假设有一个Person类,现在我要定义一个依赖属性NameProperty,代码如下:
- //依赖属性必须在依赖对象DependencyObject或其子类中定义
- class Person : DependencyObject
- {
- //CLR属性包装器,使得依赖属性NameProperty在外部能够像普通属性那样使用
- public string Name
- {
- get { return (string)GetValue(NameProperty); }
- set { SetValue(NameProperty, value); }
- }
- //依赖属性必须为static readonly,后续讲解
- public static readonly DependencyProperty NameProperty =
- DependencyProperty.Register("Name", typeof(string), typeof(Person), new PropertyMetadata("DefaultName"));
- }
代码分析:
1.依赖属性必须在依赖对象(DependencyObject)或其子类中定义,因此你能够发现绝大多数WPF控件都是继承自DependencyObject的;
2.CLR属性Name的作用之一是为了让外界在使用依赖属性时,像使用普通属性一样;
3.依赖属性约定以Property结尾;
4.依赖属性必须声明为static readonly,原因后续讲解;
5.生成依赖属性需要Register方法而不是new,方法的参数依次是CLR属性名、依赖属性对应的类型(注意:所有依赖属性真正的类型是DependencyProperty,这里的意思是依赖属性所“存储”的值的类型)、依赖属性的宿主类、依赖属性的默认元数据;
6.默认元数据有许多重载,这里使用的是只有一个参数的重载类型,传入的参数是依赖属性的默认值。
Register方法的内部机制:
很明显,自定义依赖属性的关键在于Register方法,它的内部机制是:
1.创建DependencyProperty实例dp;
2.根据CLR属性名和宿主类型名生成哈希码hashcode,用于唯一标示依赖属性;
3.在DependencyObject类中有一个全局变量:哈希表PropertyFromName,用于存储所有依赖属性的hashcode,因此在这一步检查是否已经存在相同hashcode,若无则以hashcode和实例dp作为键值对存进哈希表PropertyFromName,否则报错;
4.最后返回实例dp。
注意,hashcode并不是实例dp的哈希值,而是一个名为GlobalIndex的int全局变量,由DependencyObject内复杂的算法算出来。
GetValue和SetValue方法的内部机制:
也许这个时候你会有疑问:既然依赖属性声明为static,就代表依赖属性只有一份拷贝,那么我定义多个Person类的实例对象时,一旦改变其中一个对象的依赖属性时,其他的对象岂不是都跟着改变?这时候就需要探讨GetValue和SetValue的内部机制了,这里只说GetValue,因为弄懂了GetValue后,对SetValue的机制就自然明白了。
在GetValue函数中能够看到一个EffectiveValueEntry类的实例,它就好像是一个房间入口(Entry),进去后就能获取想要的值;在每个EffectiveValueEntry的构造函数中有一个名为PropertyIndex的参数,传入的值其实就是上面提到的唯一标示不同实例的GlobalIndex,这样子,每一个实例对象的依赖属性值所对应的入口(Entry)就不一样了;而在DependencyObject类中能够看到 private EffectiveValueEntry[] _effectiveValues 这样一个语句,当某个实例的依赖属性被读取时,就会到这个数组检索它对应的EffectiveValueEntry,如果找不到则代表依赖属性还没有被手动赋值(如Name="LiMing"),DependencyObject类的内部算法就会返回依赖属性的默认值(在Register方法第四个参数中设定)。
这样就明白了,被static关键字所修饰的依赖属性对象真正作用只是用来检索真正的属性值,而不是用来存储值的;而用于检索值的是GlobalIndex,因此为了保证GlobalIndex的稳定性,需要添加readonly关键字。
所以,依赖属性是以牺牲算法来节省内存空间
posted on 2017-07-10 19:54 bingbingzhe 阅读(127) 评论(0) 编辑 收藏 举报