WPF学习笔记——理解依赖属性

2016-11-30 20:52:21来源:CSDN作者:NA_OnlyOne人点击

前言:

在使用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关键字。

所以,依赖属性是以牺牲算法来节省内存空间。

结语:

其实依赖属性的内部机制比上述复杂的多,如果想真正理解其中的原理,还是需要各位自己去钻研。Thanks for your attention!

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台