[转]WPF的依赖属性是怎么节约内存的
WPF升级了CLR的属性系统,加入了依赖属性和附加属性。依赖属性的使用有很多好处,其中有两点是我认为最为亮眼的:
1)节省内存的开销;
2)属性值可以通过Binding依赖于其它对象上,这就使得我的数据源一变动全部依赖于此数据源的依赖属性全部进行更新。
第二点开发过WPF或者SilverLight应用程序都能无比畅快地感受它带来的好处,而在节省内存这个亮点上我们也行未能深刻地感受它带来的心理上的爽快,本人试着简单地说明依赖属性到底是怎么样为我们节省内存的。
我们先来看看传统的CLR属性,先来定义个人的类Person,简单点,只包含了Name和Coutry属性,Country默认是China。
public class Person
{
public Person()
{
Country = "China";
}
public string Name { get; set; }
public string Country { get; set; }
}
如果我们现在需要造10000个中国人,太简单了,在一个循环里实例化10000个Person就行了。如果你根本不关心程序占用内存的消耗你当然不会心痛,否则你就会喊坑爹了,因为这10000个中国人的Country都是China,但内存必须为每个China开辟空间来存放,这实在暴殄天物啊!好吧,到这里你应该知道依赖属性靠节省内存这个亮点都可以闪亮登场了,虽然我们最爽的还是前面说的第一点的一变全变的功能。
谈依赖属性DependencyProperty就绕不过要谈谈依赖对象DependencyObject,这还得从Dependency提供的两个方法说起,GetValue和SetValue。
public class DependencyObject :DispatcherObject
{
public object GetValue(DependencyProperty dp)
{
}
public void SetValue(DependencyProperty dp,object value)
{
}
}
原来DependencyProperty本身不提供获取和设置依赖属性值的操作,而是由DependencyObject来负责。DependencyObject是使用GetValue和SetValue方法通过DependencyProperty实例实现对属性值的读取和保存。注意这里所说的读取和保存的属性值其实就是DependencyProperty对应的属性值,也就是SetValue方法第二个参数object类型的value,它和DependencyProperty实例对象本身是不同的东西。如果还不能很好理解,没关系,继续往下看就明白了,我们下面就升级下Person的Country属性,使之成为真正的依赖属性。
public class Person : DependencyObject
{
/// <summary>
/// 依赖属性
/// </summary>
public static readonly DependencyProperty CountryProperty = DependencyProperty.Register("Country", typeof(string), typeof(Person), new PropertyMetadata("China"), new ValidateValueCallback(CountryValidateValueCallback));
/// <summary>
/// 依赖属性的CLR属性包装器
/// </summary>
public string Country
{
get { return (string)GetValue(CountryProperty); }
set { SetValue(CountryProperty, value); }
}
/// <summary>
/// 属性值验证
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool CountryValidateValueCallback(object value)
{
string country = (string)value;
if (country.Equals("Japan")) return false;
return true;
}
/// <summary>
/// Name是一个CLR属性
/// </summary>
public string Name { get; set; }
}
上面创建的一个依赖属性CountryProperty是用到DependencyProperty.Register最完全的参数的重载方法,关于这个方法里面各个参数的详情不是本文讨论的重点,如果你需要详细了解,可以阅读Clingingboy写的关于依赖属性的文章。DependencyProperty.Register的一个参数”Country”用来指明哪个CLR属性作为这个依赖属性的包装器,另外还需要使用这个它的HashCode,这点后面会讲到。第二个参数“typeof(string)”指明此依赖属性存储的的什么类型的值。第三个参数“typeof(Person)”用来指明此依赖属性宿主是什么类型,也需要使用它的HashCode。第四个参数“new PropertyMetadata("China")”可以指定依赖属性读取值的默认值,这里默认是“China”,第五个参数“new ValidateValueCallback(CountryValidateValueCallback)”,它指定验证值的方法,好吧,我们这个验证值的方法就把Japan先排除吧,凡是Country赋值为Japan就会抛出异常。
这里定义的CountryProperty是一个static,我们知道静态对象里面的值都是一变全变的,也就是我在一处修改了静态对象,所有引用这个静态对象的地方都会改变,这说明CountryProperty不适合来保存value这个属性值,否则假如我实例化10000个Person,这10000个Person都共用一个CountryProperty,到底用来保存哪个Person对象的Country呢?
上面实例化一个DependencyProperty没有使用new关键字,而是使用DependencyProperty.Register这个静态方法,这个静态方法大有乾坤,下面我们重点分析下这个静态方法。
我们先看看DependencyProperty类中DependencyProperty.Register的源码,这里为了阅读方便将干扰我们阅读的部分代码去掉了:
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
{
RegisterParameterValidation(name, propertyType, ownerType);
DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
return property;
}
RegisterParameterValidation(name, propertyType, ownerType)方法验证第一个、第二个和第三个参数是否为null,任何一个为null都将抛出异常,说明这三个参数在调用Register方法是必传的。
DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback),这行告诉我们内部又使用了RegisterCommon方法来实例化DependencyProperty对象。我们在来看看RegisterCommon方法的关键源码,同样只保留了关键的源码:
private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
FromNameKey key = new FromNameKey(name, ownerType);
lock (Synchronized)
{
if (PropertyFromName.Contains(key))
{
throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered, name, ownerType.Name));
}
}
// Establish default metadata for all types, if none is provided
if (defaultMetadata == null)
{
defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType);
}
// Create property
DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
// Map owner type to this property
// Build key
lock (Synchronized)
{
PropertyFromName[key] = dp;
}
return dp;
}
if (defaultMetadata == null)
{
defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType);
}
从这段代码中我们可以知道DependencyProperty.Register的第四个参数传为null的时候,会创建一个默认的元数据。
PropertyFromName[key] = dp;这句代码告诉我们创建出来的DependencyProperty对象是保持在名为PropertyFromName的全局的Hashtable中的。
private static Hashtable PropertyFromName = new Hashtable();
那么这个key是怎么来的呢,回到源码的第一句:FromNameKey key = new FromNameKey(name, ownerType),可以知道PropertyFromName中的key对象类型是FromNameKey,我们知道判断一个Hashtable中元素的key是否相同判断的是key的HashCode是否相同,所以我们下一步自然是需要看看FromNameKey的GetHashCode返回的什么:
private class FromNameKey
{
public FromNameKey(string name, Type ownerType)
{
_name = name;
_ownerType = ownerType;
_hashCode = _name.GetHashCode() ^ _ownerType.GetHashCode();
}
public void UpdateNameKey(Type ownerType)
{
_ownerType = ownerType;
_hashCode = _name.GetHashCode() ^ _ownerType.GetHashCode();
}
public override int GetHashCode()
{
return _hashCode;
}
}
现在一目了然了,我们前面提到了DependencyProperty.Register的第一个参数“Country”和第三个参数typeof(Person)需要用到它们的HachCode,这里的FromNameKey的HashCode值就是第一个参数的HashCode和第三个参数typeof(Person)的HashCode进行^运算得到的。从而我们可以推知,一个类型的同一个Dependency在全局的PropertyFromName里面只会保存一个实例对象(因为Hashtable的键值对里面不允许存在相同的键)。
通过以上分析,我们可以总结下DependencyProperty.Register的作用:
1)将一个DependencyProperty对象存储在一个全局的Hashtable中(PropertyFromName),而这个Hashtable存储的对象的Key由依赖属性对象对应的CLR属性包装器名称和依赖属性对象寄存的类型DHashCode决定。
2)DependencyProperty对象保存了一个属性元数据的默认值;
3)返回一个DependencyProperty对象实例。
DependencyProperty对象只是保存了一个默认值,那么我们调用DependencyObject的SetValue(DependencyProperty dp,object value)方法保存属性值的时候,这里面的第二个参数value到底保存到哪里去了呢?这就需要来阅读下DependencyObject类的源码了,阅读源码我们发现DependencyObject类有一个私有数组变量:
private EffectiveValueEntry[] _effectiveValues;
当我们调用DependencyObject的GetValue(DependencyProperty dp)方法的时候,会根据DependencyProperty 对象的GlobalIndex属性判断EffectiveValueEntry[] 是否包含这个依赖属性对象的属性值,如果没有就返回依赖属性的默认值。理解这点很关键,这是依赖属性节省内存的关键之一,因为一个类里面的依赖属性对象的静态的,也就是所有的实例化对象都是公用这个依赖属性对象,回到我们开篇的话题,当你实例化10000个Person的时候,如果没有调用Person的SetValue方法,那么读取这10000个Person的Country属性时,都是从一个依赖属性对象的CountryProperty的默认值读取的,这个时候内存中只用一个地址存放“China”就可以了,大大节省了内存的开销。
当我们调用DependencyObject的SetValue(DependencyProperty dp,object value)方法时,就会将这个值保存在EffectiveValueEntry[]某个EffectiveValueEntry类型的元素上。
在WPF中大部分UI控件都有很长的继承体系,一个控件通过继承而来的属性就有一箩筐了。另外将依赖属性定义在控件的父类里面,那么这个父类所有的子类对象都会共享依赖属性对象,可见依赖属性的使用大大降低了控件对内存的消耗。
希望这篇文章对于你理解WPF中的依赖属性节省内存的机制有所帮助。笔者水平有限,如果说的不对,请高手斧正。
后记:成文后,我想起了string类型在.NET中采用享元模式,也就是说string a = "China"和string b = “China”两个变量a和b其实指向同一个地址的。在本文中的Person类中CountryProperty存储的数据类型是一个string,因为.NET对string的特殊优化,所以定义一个存储string类型DependencyProperty来说明节省内存不太合适,假如我创建10000个Person对象而言,那么采用依赖属性的优势就是节省了存放指向“China”地址的引用的地址——这么说来还是有节省内存的优势,只不过如果用其他类型如List<Int>类型来写这个文章更合适些了。
转载来源 :https://www.aliyun.com/jiaocheng/619456.html
[转]WPF的依赖属性是怎么节约内存的的更多相关文章
- WPF的依赖属性和附加属性(用法解释较全)
转:https://www.cnblogs.com/zhili/p/WPFDependencyProperty.html 一.引言 感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己 ...
- WPF的依赖属性
Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR)属性的功能,这些服务通常统称为 WPF 属性系统.由 WPF 属 ...
- wpf 的依赖属性只能在loaded 事件之后才能取到
wpf 的依赖属性只能在loaded 事件之后才能取到,在构造函数的 InitializeComponent(); 之后取不到 wpf 的依赖属性只能在loaded 事件之后才能取到,在构造函数的 ...
- WPF 中依赖属性的继承(Inherits)
WPF中依赖属性的值是是可以设置为可继承(Inherits)的,这种模式下,父节点的依赖属性会将其值传递给子节点.例如,数据绑定中经常使用的DataContextProperty: var host ...
- WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性
原文:WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性 如果你要自定义一个图片按钮控件,那么如何在主窗体绑定这个控件上图片的Source呢? ...
- WPF利用依赖属性和命令编写自定义控件
以实例讲解(大部分讲解在代码中) 1,新建一个WPF项目,添加一个用户控件之后在用户控件里面添加几个控件用作测试, <UserControl x:Class="SelfControlD ...
- WPF: 只读依赖属性的介绍与实践
在设计与开发 WPF 自定义控件时,我们常常为会控件添加一些依赖属性以便于绑定或动画等.事实上,除了能够添加正常的依赖属性外,我们还可以为控件添加只读依赖属性(以下统称"只读属性" ...
- WPF 自定义依赖属性
原博客地址:http://www.cnblogs.com/DebugLZQ/archive/2012/11/30/2796021.html DependencyObject和Dependen ...
- WPF 之 依赖属性与附加属性(五)
一.CLR 属性 程序的本质是"数据+算法",或者说用算法来处理数据以期得到输出结果.在程序中,数据表现为各种各样的变量,算法则表现为各种各样的函数(操作符是函数的简记法). ...
随机推荐
- wdcp后台登陆访问失败处理方法
用putty或xsheel链接 进入之后输入命令 service wdcp restart 之后显示ok就成功了
- strxfrm - 转换字符串
总览 (SYNOPSIS) #include <string.h> size_t strxfrm(char *dest, const char *src, size_t n); 描述 (D ...
- Linux下Golang Socket编程原理分析与代码实现
在POSIX标准推出后,socket在各大主流OS平台上都得到了很好的支持.而Golang是自带Runtime的跨平台编程语言,Go中提供给开发者的Socket API是建立在操作系统原生Socket ...
- jsp EL运算符
算术运算符 算术运算符 说明 示例 结果 + 加 ${1 + 1} 2 - 减 ${1 - 1} 0 * 乘 ${1 * 2} 2 /或div 除 ${3 / 2} 1.5 %或mod 取余 ${3 ...
- PHP ftp_put() 函数
定义和用法 ftp_put() 函数上传本地一个文件到 FTP 服务器上. 如果成功,该函数返回 TRUE.如果失败,则返回 FALSE. 语法 ftp_put(ftp_connection,remo ...
- Android中的onWindowFocusChanged()方法详解
Android中获取手机屏幕的高度和宽度,我们知道在onCreate方法中获取到的值都是为0的,有人说可以在onClick方法中获取值,这个也是个方法 ,但在onWindowFocusChanged方 ...
- 剑指offer——二进制中1的个数(c++)
题目描述实现一个函数,输入一个整数,输出该数二进制表示中1的个数.例如,把9表示成二进制是1001,则输出为2 常规解法首先把n和1做位运算,判断n的最低位是不是1,然后把1左移一位得到2,再把n和2 ...
- 搭建hadoop集群 单机版
二.在Ubuntu下创建hadoop用户组和用户 这里考虑的是以后涉及到hadoop应用时,专门用该用户操作.用户组名和用户名都设为:hadoop.可以理解为该hadoop用户是属于一 ...
- checkbox、radio使用jquery改变状态以及其他操作
$('input[type=checkbox]:checked').each(function(index,elem){ $(elem).attr("checked",false) ...
- 20140724 菜单制作:制表位(段落->制表位->)
1.菜单制作:制表位(段落->制表位->) 叶轩楠·········· 上海大学 轩楠叶·········· 上海大学 楠轩叶·········· 上海大学 选完后要选“设置” 2.光盘制 ...