1、属性的来龙去脉
程序的本质就是:数据+算法=>以算法来处理数据以期待得到的输出的结果
然后到了面向对象的时代,类这一数据结构出现了,它把散落在程序中的变量跟函数归档封装,被封装的变量成为Field,函数成为Method
我们还可以控制它的可访问性,如private,public,
是否使用static关键字决定了字段或方法对类有意义还是对类的实例有意义
对类的实例有意义:比如Human类,Weight字段则对但个个体(实例)有意义
对类有意义:Amount总量,对但整个人类有意义
静态字段在内存中只有一个拷贝,非静态字段每个实例都有一个拷贝
而无论方法是否是静态的,在内存中只有一个拷贝
而直接给字段赋值容易把错误的值写入字段,所以每次都需要判断一下,这样会增加代码冗余和违反高内聚的原则
该功能只能交给对象自己来处理,所以属性出现了
属性一般是这样的
Class Human{
private Int32 age;
public Int32 Age{
get{ return age;}
set
{
if(value>=0||value<=100)
{
this.age=value;
}
else{
throw new OverFlowException("Age overflow");
}
}
}
}
如果实例化多个Human
Human h1=new Human();
Human h2=new Human();
Human h3=new Human();
这样age字段在内存中会有3个拷贝,会消耗一定内存,而对于age的包装器(CLR属性),通过IL看出会生成对应的两个方法get_Age:Int32(),set_Age:void(Int32),
再多的实例方法也只有一个拷贝,所以CLR属性不会增加内存的负担
2、依赖属性(Dependency Property)
依赖属性主要解决的是内存消耗的问题
想想啊一个TextBox对象包含着138个属性,每个属性都包装着一个4个字节的字段,如果一个页面上有10*1000个TextBox,那么则会消耗内存138*4*10*1000≈5.26MB内存
而我们最常用的是其Text属性,意味着大多数内存被浪费掉了
依赖属性可以解决这个问题,依赖属性就是一个可以没有值,对象创建时可以不包含用于存储数据的空间(即字段占用的空间),
通过Binding从数据源获取值(依赖在别人身上的属性),拥有依赖属性的对象成为依赖对象
依赖属性的特点:
节省实例对内存的开销
属性值可以通过Binding依赖在其他对象上
依赖对象(DependencyObject)通过依赖属性(DependencyProperty)来获取数据
依赖对象被DependencyObject所实现
public class DependencyObject:DispatcherObject{
public object GetValue(DependencyProperty dp){ //.... }
public void SetValue(DependencyProperty dp,object value){ //...}
}
WPF所有UI控件都是依赖对象,UI控件的大多数属性都已经依赖化了。
举例:
建立一个依赖对象
public class Student:DependencyObject{
//建立一个依赖属性
/**
*1.DependencyProperty一定存在于DependencyObject中,所以Student继承DependencyObject
*2.public static readonly 来修饰,NamePropert成员变量名字后加Property表明他是一个依赖属性
*3.非new而是通过DependencyProperty.Register来注册
* 参数1:将来使用哪个CLR属性作为这个依赖属性的包装器
* 参数2:依赖属性用来存储什么类型的值
* 参数3:该依赖属性的宿主是什么,即他依赖对象的类型
*/
public static readonly DependencyProperty NameProperty=
DependencyProperty.Register("Name",typeof(string),typeof(Student));
}
注意:
1.依赖属性就是那个有public static readonly修饰的DependencyProperty的实例,而依赖属性的包装器(Wrapper)是个CLR属性,不要把包装器误认为是依赖属性,
没有包装器这个依赖属性一样存在
2.包装器的作用是以“实例属性”的形式向外界暴露依赖属性,这样,一个依赖属性才能成为数据源的一个Path
3.上面第二个参数,工作中习惯性称其为“依赖属性的类型”,其实应该称为“依赖属性的注册类型”,因为依赖属性的类型是DependencyProperty。
string str1=“从我这可以获取到值”;
Student s=new Student();
s.SetValue(Student.NamedProperty,str);
string str2=s.GetValue(Student.NamedProperty).ToString();
Console.WriteLine(str2);//从我这可以获取到值
依赖属性即使没有CLR属性作为其外包装器,也能很好的工作。
因为在GetValue时我们要进行一次数据转换object->string,为了体现高内聚原则所以我们在给依赖属性加上一个外包装器
public string Name
{
get{ return (string)GetValue(NameProperty);}
set{ SetValue(NameProperty,value);}
}
有了这个包装,就相当于为依赖对象准备了用于暴漏数据的Binding Path
这样Student对象既可以扮演数据源和数据目标的双重角色,虽然没有实现INotifyPropertyChanged接口,当属性值发生改变时与之Binding的对象依然能得到通知
哇塞,天生的就是合格的数据源。
对于WPF的UI控件都有一个SetBinding的方法
Binding binding=new Binding("Text"){Source=txtBox1};
txtBox2.SetBinding(TextBox.TextProperty,binding);
对于Setbinding方法Student类中没有,DependencyObject中也没有,其实这个方法在FrameworkElement类中,
public class FrameworkElement{
public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding)
{
//尼玛仅仅对BindingOperations.SetBinding做了个简单的封装。。。。。
return BindingOperations.SetBinding(this,dp,binding);
}
}
OK 我们也对Student 简单的封装一个SetBinding方法
public class Student :DependencyObject{
//..
public BindingExpressionBase SetBinding(DependencyProperty dp ,BindingBase biding)
{
return BindingOperations.SetBinding(this,dp,binding);
}
}
小技巧:propdp按tab键就可以修改依赖属性的各个参数
其实在注册DependencyProperty实例的时候,DependencyProperty.Register()方法还有第四个参数的重载,类型为PropertyMetadata
作用是给依赖属性的DefaultMetadata属性赋值,意思是说DefaultMetadata是向依赖属性的调用者提供一下信息:
1.CoerceValueCallback:依赖属性值被强制改变时此委托会被调用,此委托可关联一个可影响的函数。
2.DefaultValue:依赖属性未被显示赋值,若读取之则获取次默认值,不设置此值会抛异常。
3.IsSealed:控制PropertyMetadata的属性值是否可以改变,默认值为true。
4.PropertyChangedCallback:依赖属性值改变后悔调用此委托,此委托可关联一个可影响的函数。
需要注意的是:依赖属性的DefaultMetadata只能通过Register方法的第四个参数进行赋值,一旦赋值不能改变(DefaultMetadata是个只读属性),
如果想用新的DefaultMetadata来替换掉默认的metadata,则需要用到DependencyProperty.OverrideMetadata()方法。
深入探讨一下依赖属性的值是如何存取的。。。
我们知道DependencyObject中有个SetValue方法
DependencyProperty中?No。因为它是一个static对象,如果成百上千的实例难道都存在一个对象里?No
在DependencyProperty这个类中会有这样一个成员
private static Hashtable PropertyFromName=new Hashtable();
这个就是用来注册DependencyProperty实例的地方。。
所有DependencyProperty.Register()方法的重载都归根于对DependencyProperty.RegisterCommon方法的调用
private static DependencyProperty RegisterCommon(
string name,
Type propertyType,
Type ownerType,
PropertyMetadata defaultMetadata,
ValidateValueCallback validateValueCallback
){
//1.参数为依赖属性名称和依赖属性所属的依赖对象
//两个参数进行异或运算
FromNameKey key=new FromNameKey(name,ownerType);
//...
//2.检查该依赖属性是否被注册过
if(PropertyFromValue.Contains(key))
{
//如果你尝试同一个属性名称和宿主类型进行注册则会抛出异常
throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered,name,ownerType.name));
}
//3.检查PropertyMetadat是否提供,如果没有则准备一个默认的
//...
//4.都准备妥当DenpendencyProperty被创建出来了
DependencyProperty dp=new DependencyProperty(name,propertyType,ownerType,defaultMetadata,validateValueCallback);
//5.注册进Hashtable中去,key会自动调用其重写的GethashCode()
PropertyFromName[key]=dp;
}
前四个参数与Register中的都一样,那到底在FromNameKey中做些什么呢,下面是FromNameKey对象的构造器
public FromNameKey(string name,Type ownerType){
_name=name;
_ownerType=ownerType;
//依赖属性名称与依赖对象的异或运算
_hashCode=_name.GetHashCode()^_ownerType.GetHashCode();
}
并且Override中有其GetHashCode方法的重写
public override GetHashCode(){
return _hashCode;
}
所以key变量的hashcode是RegisterCommon方法的第一个参数CLR属性名字字符串与第二个参数依赖对象的类型的异或运算
这样每对"CLR属性名称-宿主类型"所决定的DependencyProperty实例就是唯一的。
最后DependencyProperty以“Key-Value”的形式存入全局名为PropertyFromName的hashtable中
WPF属性系统通过CLR属性名称-宿主类型就可以在这个hashtable中取出对应的DependencyProperty实例。
注意:注册后生成的DependencyProperty实例的hashCode与存于全局表中的通过异或运算得出的hashCode是不一样的,即key值不等于value的hashCode
每个DependencyProperty实例都有一个名为GlobalIndex的int类型属性,该属性是通过一些算法实现的,确保每一个DependencyProperty实例都是唯一的
其实唯一一点很重要的是GlobalIndex属性值就是DependencyProperty的哈希值。
在谈谈附加属性
该属性的本质也是一种依赖属性,说是一种属性不属于某个对象,但由于某种需求而被后来附加上的,表现为被环境附加上的属性。
一般我们在什么时候用到附加属性呢,比如有一个Human类,它可能被学校相关的工作流用到(记录它的专业、年纪、班级),也可能被某一个公司的工作流用到(记录它的部门、项目)
宿主:School,1个Grade附加属性
class School :DependencyObject{
public static readonly DependencyProperty GradeProperty=
DependencyProperty.RegisterAttached("Grade",typeof(int),typeof(School),new UIPropertyMetadata(0));
public static int GetGrade(DependencyProperty obj){
return (int)obj.GetValue(GradeProperty);
}
public static SetGrade(DependencyProperty obj,object value){
obj.SetValue(GradeProperty,value);
}
}
如何消费这个Grade附加属性
class Human:DependencyObject{
public Human{()
{
Human h=new Human();
School.SetGrade(h,1);
int grade=School.GetGrade(h);
Console.WriteLine(grade);
}
}
在WPF的现实工作中常常会遇到下面代码:
<Grid>
<Grid.ColumnDefintions>
<ColumnDefinitions/>
<ColumnDefinitions/>
<ColumnDefinitions/>
</Grid.ColumnDefintions>
<Grid.RowDefinitions>
<RowDefinitions/>
<RowDefinitions/>
<RowDefinitions/>
</Grid.RowDefinitions>
<Button x:Name="btn_OK" Grid.Row="1" Grid.Column="1" />
</Grid>
这里Grid.Row Grid.Column都是附加属性
等价的C#代码
Grid grid=new Grid();
//添加行
//添加列
Button button=new Button(){ Content="OK"};
Grid.SetColumn(button,1);
Grid.SetRow(button,1);
Grid.Children.Add(button);
this.Content=grid;