剖析WPF依赖属性

    这节来讲一下WPF中的依赖属性 (Dependency Property)

【了解属性和字段】

    我们知道,属性是面向对象语言中用来封装字段的外衣,它像是字段对外界的桥梁,我们可以通过属性来验证数据的合法性或控制对外的访问性等等。每个属性的背后都有其对应的一个字段做支撑,就算是自动属性,在编译时系统也会创建其字段,只不过自动属性是微软给我们的语法糖罢了。在C#中,属性最后是会编译成两个方法:get_属性名set_属性名(如果是只读属性,则没有set方法,反之没有get方法)。

    编译成方法,属性就不会占用太多空间,因为方法存在于内存公共的方法区,每个实例的创建不过是多一个指向该方法的指针。但是字段不一样,每个实例创建的创建,都会在内存中开辟对应的空间来存放字段,一个类中的字段越多,它在内存中占用的空间就越大,理解了这个理论,下面我们来正式说明什么是依赖属性,为什么要有依赖属性。

    

【什么是依赖属性】

    我们使用一个控件,可以看到这个控件有很多的属性,有属性就有字段的内存开销,但实际上对于一个控件,我们大多数只会使用其部分常用属性,比如Button我们最常使用Content,Height等属性,那些不经常使用的属性相当于白白占用着内存。当我们写一个复杂的XAML页面,涉及到很多控件的使用时,这种浪费内存的现象就很严重。

    对此,微软在WPF中引入了依赖属性(Dependency Property),依赖属性允许没有自己的字段,可以通过Binding绑定到其它对象的属性或者说数据源上,从而获得值,这种依赖在其它对象上的属性,就是依赖属性,当明确了它的功能,我想大家就不会对依赖二字产生疑惑了,依赖属性没有自己的字段,只在使用时通过Binding从别的对象身上获取,给自己临时创建内存空间,这样不使用就不会有多余内存消耗。

    包含依赖属性的对象称为依赖对象(Dependency Object),这种对象需要继承DependencyObject这个基类,实际上,WPF中的控件,都继承了DependencyObject这个类,控件中的大部分属性都是依赖属性,这样我们才能通过Binding去绑定值(不熟悉Binding的同学可以参见前文Binding(一):数据绑定系列),才不会有内存浪费现象的发生。

【从代码中学习依赖属性】

    下面我们通过代码来学习一下如何声明并使用依赖属性,请先看我写好的一段代码:

public class Pikachu : DependencyObject
{public static readonly DependencyProperty PikachuNameProperty =DependencyProperty.Register("PikachuName",typeof(string),typeof(Pikachu));
}

    上文说到,使用依赖属性必须要继承DependencyObject类,另外,声明

依赖属性,需要使用public static readonly三个修饰符修饰,实例依赖属性也不是通过new操作符,而是通过DependencyProperty的Register方法来获取

    依赖对象的名字,有个约定,就是以Property为后缀,在C#中有很多命名约定,比如接口用I做前缀,特性用Attribute做后缀等等,这样做都是为了有个良好的命名规范,做到见名知意。

    Register方法有三个重载,此处用的是其三个参数的重载,它还有四个参数和五个参数的重载。

  • 第一参数是指定依赖属性的包装器名称是什么(包装器就是用来包装依赖属性的,通过一个属性来包装依赖属性供外部使用,具体下文会讲,此处先做了解)

  • 第二个参数是指定依赖属性要存储的值的类型是什么

  • 第三个参数是指定依赖属性属于哪个类的,或者说是为哪个类定义依赖属性

  • 其它重载中第四个参数是指定依赖属性的源数据,用于提供给调用者此依赖属性的信息

  • 其它重载中第五个参数是自定义的依赖属性生成时的验证回调

    声明了依赖属性,但是如何给依赖属性赋值呢,这就要用到DependencyObject基类中的方法了,我们使用其中的SetValue方法和GetValue方法来操作依赖属性的值,请看下面改动后的代码:

public class Pikachu : DependencyObject
{public string PikachuName {get => (string)GetValue(PikachuNameProperty); set => SetValue(PikachuNameProperty, value); }public static readonly DependencyProperty PikachuNameProperty =DependencyProperty.Register("PikachuName", typeof(string), typeof(Pikachu));
}

   上述代码,就是一个比较完善的声明依赖属性并通过包装器将依赖属性暴露出去的例子,属性PikachuName就是依赖属性的包装器,在get块中通过GetValue方法传入依赖属性的名字获取依赖属性的值,在Set块中通过SetValue方法,给依赖属性赋值,对依赖属性的这层包装,使得我们在外部操作依赖属性变得简单,这也是为什么我们在正常使用中感觉不到依赖属性的存在,因为字段也好,依赖属性也好,我们在外部看到的操作的都是它的属性。

    下面通过一个实例展示一下依赖属性的使用:

    前台代码是一个名为btn_show的Button控件,后台代码如下:

public MainWindowBase()
{InitializeComponent();this.DataContext = this;Data = "我是皮卡丘";Pikachu pikachu = new Pikachu(); //使用Binding操作类将皮卡丘对象的皮卡丘名字依赖属性关联到Data上BindingOperations.SetBinding(pikachu,Pikachu.PikachuNameProperty, new Binding("Data") { Source = this });//将按钮的Content依赖属性绑定到皮卡丘的皮卡丘名字包装器上btn_show.SetBinding(Button.ContentProperty, new Binding(nameof(pikachu.PikachuName)) { Source = pikachu });
}

    这个例子的逻辑是有一个名为Data的属性作为数据源,先将皮卡丘对象的依赖属性绑定到Data数据源上,再将Button的Content依赖属性绑定到皮卡丘对象的依赖属性包装器上,这就形成了一个Binding链,运行效果如下:

    整个过程中,只有Data属性是有字段在背后支撑的,它存储了“我是皮卡丘”这个数据,皮卡丘对象和Button对象都是依赖属性,不占内存空间,它们之间使用Binding关联,形成数据通道,这样就实现了一块内存,供给多处使用。按照之前的编程模式,需要皮卡丘和Button各自开辟一段空间存储Data来的数据,现在由三块内存节省为一块内存,这就是依赖属性对于节省内存的效果。

【从源码分析依赖属性】

    下面我们来分析一下,为什么依赖属性不是用new实例,而是要注册,以及Get/SetValue的操作依赖属性值的原理。

    我们先从Register方法看起:

    Register的三个和四个参数的重载都指向了五个参数的重载,我们主要看一下这五参数重载的方法里边都有什么。方法体里边,前几行实际上是一些验证代码,当参数有误时,会抛出异常。紧接着的是一个返回依赖属性对象的RegisterCommon方法,从名字和返回值来看这就是最核心的方法了,我们接着跟进去看:

    代码内部第一行使用FromNameKey生成了一个key对象,这个FromNameKey是Dependency类的一个内部类,它构造器需要传入的包装器名称和依赖对象所在的类的Type,    这个类及构造器代码如下:

    构造器第三行代码比较重要,我们可以看到,这个类通过传入的参数两者异或生成了一个hashcode,经过这个异或运算,那就保证了同一个类,同样的包装器名称生成的hashcode是一样的。

    同时这个类重写了GetHashCode方法,就是把异或生成的hashcode返回出去了。

    了解了这个类,我们再回到RegisterCommon类中,接着往下看,下面是一个线程同步块:

    这个代码块里边,出现了一个PropertyFromName参数,看样子是个集合,我们找到这个属性的定义处,发现它是个全局的HashTable:

    那这个代码块的意思就明了了,目的就是判断生成的Key是否已存在,如果存在,就抛异常,从这里就控制了,在类内部定义两个相同包装器名称的依赖属性是不允许的,实际上也必须是这样,同一个类中,属性肯定是不能同名的,依赖属性也是如此,那我们从此处还能获得一个信息,就是PropertyFromName肯定和我们要生成的依赖属性有很大的关系,具体我们继续往下看代码:

    如果没有传入依赖属性的源数据,系统会生成默认的源数据,在往下看是一些校验逻辑,具体内容此处就不分析,有兴趣的可以自己点进去看,紧接着就到代码核心了:

    经过层层把关,依赖属性终于new出来了,new出来后,下面我们又看到PropertyFromName的影子了:

    原来PropertyFromName是存储依赖属性的一个集合,所有new出来的依赖对象都存储在这里,它的hashcode就是之前通过FromNameKey类异或出的。

    最后,通过return,返回了这个依赖属性 ,至此,依赖属性的整个创建过程解析完毕。

    我们再来了解一下依赖属性的值的读取:

    先看GetValue方法:

    前几句代码还是校验,核心代码是最后一句,此处涉及到了依赖属性的GlobalIndex属性,这个属性是系统经过一系列算法得出的,具有唯一性,我们看到,这个GlobalIndex传入了名为LookupEntry方法中,Entry是入口的意思,从方法名上看,我们能得知,是根据GlobalIndex找到了一个访问入口,实际上,这个入口就是依赖属性值的访问入口。

    我们进入GetValueEntry方法中查看,会找到一个名为_effectiveValues的属性,这是一个EffectiveValueEntry类型的数组,原来,依赖属性所有的值都存放在这个数组中,根据依赖属性唯一的GlobalIndex,我们就能从这个数组中找到依赖属性的值。

    再来看SetValue方法:

    其实明白了GetValue,SetValue也就很好理解了,道理都是一样的,根据依赖属性的GlobalIndex值获取到入口,更新上新值,我们进入SetValueCommon方法中看,代码比较繁琐,实际上的流程有三块:

  • 判断值是不是DependencyProperty.UnsetValue,如果是,则清除依赖属性的值,所以我们要想对依赖属性设置空值,不要用null,要用DependencyProperty.UnsetValue

  • 判断能否找到入口,如果没有入口,则新建一个入口对象,将值放进去,有入口则更新值

  • 最后,通过UpdateEffectiveValue方法对依赖属性的值做一些处理

    至此依赖属性的读取流程解析完毕。

  

    本节到此结束...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300558.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你在孩子身上偷的懒,终将会变成最大的遗憾

全世界只有3.14 %的人关注了青少年数学之旅我们来看一个非常有趣的统计:2007年—2016年全国高考状元父母职业统计最优秀的孩子大多数出自教师家庭。很家长说,教师有着和孩子一样的寒暑假,有着教书育人的先天优势,我们普通人工作忙…

开发们 点广告-赚点BT币

2019独角兽企业重金招聘Python工程师标准>>> http://freebitco.in/?r14320 转载于:https://my.oschina.net/wangtao/blog/180765

Asp.net页面的生命周期

介绍Asp.net是微软.Net战略的一个组成部分。它相对以前的Asp有了很大的发展,引入了许多的新机制。本文就Asp.net页面的生命周期向大家做一个初步的介绍,以期能起到指导大家更好、更灵活地操纵Asp.net的作用。当一个获取网页的请求(可能是通过…

太赞了!微软《dotnet中文手册》火了,完整PDF开放下载!

这是微软高级架构师基于最新的.net编写,循序渐进地对.net/C#进行讲解。对于零基础可以作为.net的快速入门教材,对于高级程序员而言,这也是你的进阶之路,今天来了,就是缘份,看到了就送给你!资料介…

你穿衣品味还不如AI,这有一款时尚着装网络模型

全世界只有3.14 %的人关注了青少年数学之旅有一件衣服的时候,怎样的小改动可以提升其整体的时尚性?近日 UT 奥斯汀、康奈尔大学、乔治亚理工和 Facebook AI 研究中心的研究者提出了一种名为 Fashon 的模型,用于给一件衣服进行改进&#xff0…

分割移动微小物体

思路&#xff1a;1. 前后两帧相减&#xff0c;得到差分图像2.在差分图像里求像素最大的点&#xff08;最亮的点&#xff09;这样就得到&#xff0c;移动微小物体#include "check.h"#include <iostream.h>voidmain(){ DWORD t1,t2; IplImage *frame0; …

一个非常实用的Python SSH库

前言 Python的Paramiko库&#xff0c;它是一个用于实现SSHv2协议的客户端和服务器的库。通过使用Paramiko&#xff0c;我们可以在Python程序中轻松地实现远程服务器的管理、文件传输等功能。特别做智能硬件产品的同学要熟悉它&#xff0c;因为它能为你减少很多麻烦&#xff0c…

在腾讯,我的试用期总结!

在腾讯的 3 个月&#xff0c;我经历了些什么&#xff1f;大家好&#xff0c;我是鱼皮。最近不少小伙伴顺利毕业&#xff0c;入职了新公司&#xff0c;开始了人生第一段正式工作经历。但对于很多公司&#xff0c;如果你想要成为正式员工&#xff0c;是需要先经历几个月的试用期的…

这6个动作,据说只有20%的人能做到!| 今日最佳

全世界只有3.14 %的人关注了青少年数学之旅你的手指够灵活吗&#xff1f;能做出下面这些动作在回答吧。据说就是下面这6个动作&#xff0c;据说只有20%的人能做到哦&#xff01;大家不妨试试&#xff0c;看看你是不是这20%中的一员。PS&#xff1a;反正笔者已经十指已打结了...…

打造个性化的Internet Explorer

作者&#xff1a;孙辉 在Microsoft的软件哲学中&#xff0c;框架窗口是一个十分重要的角色&#xff0c;这类窗口简直无处不在。所谓框架窗口&#xff0c;就是四个窗口边上具有停靠对象能力的窗口对象&#xff0c;从现象上看&#xff0c;框架窗口有十分特别的“边”&#xff0c;…

百度家电行业报告摘录

上图反映了关注家电信息的网民在百度频道的活跃情况。很明显&#xff0c;百度贴吧、百度图片和百度知道是网民最活跃的频道&#xff0c;特别是百度贴吧&#xff0c;有超过四分之一的检索量都来自于百度贴吧。百度有不少以家电品牌或产品为主题的贴吧&#xff0c;比如海尔吧、TC…

如何识别能把桥压塌的大车?快看!能救命!

全世界只有3.14 % 的人关注了青少年数学之旅2019年10月10日晚6:10左右&#xff0c;江苏无锡市北环路附近一高架桥出现桥面侧翻&#xff0c;经现场初步勘测&#xff0c;桥下被压小车3辆&#xff08;其中一辆系停放车辆&#xff0c;无人&#xff09;。经搜救确认&#xff0c;事故…

如何用outlook express 收发邮件

一&#xff0e;创建用户1.打开Outlook express软件&#xff0c;单击“工具”选项&#xff0c;选择“帐户”选项。2.单击“添加”按钮&#xff0c;选择“邮件”选项。3.在“连接向导”中&#xff0c;输入发件人姓名&#xff0c;在对方收到邮件后&#xff0c;发件人姓名将会显示在…

java虚拟机 山寨机_十年前的山寨机居然有系统?没错,还是纯国产的

文丨太平洋数码苍茫的天涯是我的爱&#xff0c;绵绵的青山脚下花正开&#xff0c;什么样的节奏是最呀最摇摆……你是我天边最美的云彩&#xff0c;让我用心把你留下来(留下来)……不知道大家是否还记得这段熟悉的旋律&#xff0c;总之小编我今天再次回听这首歌的时候身体依然会…

dotnet中的counters说明(一)

在dotnet中&#xff0c;常用的收集指标有&#xff1a;System.Runtime 计数器Microsoft.AspNetCore.Hosting计数器Microsoft.AspNetCore.Http.Connections计数器Microsoft-AspNetCore-Server-Kestrel计数器System.Net.Http计数器(>.NET5可用)System.Net.NameResolution计数器…

当才华还撑不起梦想时,你应该静下心来看这些

全世界只有3.14 % 的人关注了青少年数学之旅在工作之余&#xff0c;我们大量的碎片时间被手机占据。无意识的刷手机打发无聊&#xff0c;不如有趣又高品质的积累。我们特意精选了在不同领域的几个高品质公众号代表&#xff0c;希望让你在快乐打发闲暇时光的同时&#xff0c;也能…

.net core ——微服务内通信Thrift和Http客户端响应比较

1、Benchmark介绍wiki中有定义&#xff1a;基准测试是运行计算机程序&#xff0c;一组程序或其他操作的行为&#xff0c;以便评估对象的相对性能&#xff0c;通常是通过对其运行许多标准测试和试验。目前许多成熟的github开源项目&#xff0c;均采用Benchmark测试结果作为性能依…

Discuz添加自定义模板广告

在做Discuz中广告的时候碰到个大问题&#xff0c;现在我需要做一个轮播的通屏广告位&#xff0c;调用广告图片的代码应该是以下代码&#xff1a;<ul> <li style"background:url(图片地址) no-repeat center top;"><a href"#"></a…

我居然手写了Spring框架

手写完了刚参加工作那会接触java还是用的struct的时代&#xff0c;后面在SSH火爆时代的时候我转战.net,多年之后公司转java技术栈已经是Spring的天下&#xff0c;源码嚼了很多遍于是很想尝试把这套东西用在.net平台上。社区有个Spring.net项目已经多年不维护了&#xff0c;而且…

下班以后看什么,决定你人生的高度

全世界只有3.14 % 的人关注了青少年数学之旅王小波说&#xff1a;我活在世上&#xff0c;无非想要明白些道理&#xff0c;遇见些有趣的事&#xff0c;倘能如我所愿&#xff0c;我的一生就算成功。你的圈子将决定你的人生。每一位对事物都有着独特的态度让你成为一个有趣的人。今…