首先,我们分析一下依赖属性产生的一个原因:
由于一个类型的依赖属性大多数情况下都保持默认值,例如几百个button的fontsize一般都是一样的,甚至整个一个软件的fontsize都是一种。那么每个对象保存一个fontsize就显得没有那个必要了,我们必须通过一个方式来保存这个属性,来让所有一个类的对象共享这个属性。我们的想法是把他做成一个静态的值。但是如果直接定义一个:
public static string name = string.empty;
但是我们可以看到这样的话,这个属性要是被设置的话,就会将所有的对象的这个属性全部都设置了。因此我们需要一个数据结构来存储这个值。
当要是当前对象设置了自己本身的这个属性的话,就在本地存储一个关于这个属性的值,来保证不会因为一个对象设置了这个值而导致所有的对象该属性都发生变化。
考虑到可能会涉及到属性的继承,所有我们还需要在数据结构中加入当前类型。
所以一个依赖属性应该包括以下内容:name,propertyType,ownerType,defaultValue。
我们用一个字典来存储这些所有的属性,这样的话就可以提高效率。
class MyDependencyProperty { internal static Dictionary<object,MyDependencyProperty> RegisteredDps = new Dictionary<object,MyDependencyProperty>(); internal string Name; internal object Value; internal object HashCode; private MyDependencyProperty(string name,Type propertyName,Type ownerType,object defaultValue) { this.Name = name; this.Value = defaultValue; this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode(); } public static MyDependencyProperty Register(string name,Type propertyType,object defaultValue) { MyDependencyProperty dp = new MyDependencyProperty(name,propertyType,ownerType,defaultValue); RegisteredDps.Add(dp.HashCode,dp); return dp; } }
我们设计了私有的构造函数,只通过Register方法来添加属性,这样可以保证一切的依赖属性都是经过我们的验证的。并且所有的依赖属性一旦都早出来就会立即添加入数据集合。
当我们一个类需要使用这个依赖属性,可以是如下方式
class MyDependencyObject
{
public static readonly MyDependencyProperty NameProperty =
MyDependencyProperty.Register("Name",typeof(string),typeof(MyDependencyObject),string.Empty);
public object GetValue(MyDependencyProperty dp)
{
return MyDependencyProperty.RegisteredDps[dp.HashCode].Value;
}
public void SetValue(MyDependencyProperty dp,object value)
{
MyDependencyProperty.RegisteredDps[dp.HashCode].Value = value;
}
public string Name
{
get
{
return (string)GetValue(NameProperty);
}
set
{
SetValue(NameProperty,value);
}
}
}
这样的话所有这个类的对象都不在拥有这个属性的内存,而是所有对象公用MyDependencyProperty类的静态数据集合RegisteredDps来存储所有的属性对象(MyDependencyProperty)。但是这个设计还有一定的问题,虽然解决了所有的依赖属性都存储在一个数据结构中的问题,但是没有解决同一个类的多个对象设置一个依赖属性时,所有的依赖属性都会被设置的问题。所以我们需要在本地添加一个数据集合,当我们设置这个属性的时候,就在这个数据集合中存储这个值,再次之后所有需要这个属性的值都是从本地存储的数据集合中找到的。
class MyDependencyObject { //这个list用来存储设置的依赖属性值,包括这个类的所有的依赖属性,都会在设置后存储在这里 private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>(); public static readonly MyDependencyProperty NameProperty = MyDependencyProperty.Register("Name",string.Empty); public object GetValue(MyDependencyProperty dp) { //在数据结构中查找对应的依赖属性是否在本地有值。 EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index); if (effectiveValue.PropertyIndex != 0) {//如果获取的元素的PropertyIndex不等于0,说明本地存在这个属性的值 return effectiveValue.Value; } else {//本地不存在就去Dictionary中区查找。 return MyDependencyProperty.RegisteredDps[dp.HashCode].Value; } } public void SetValue(MyDependencyProperty dp,object value) { //在数据结构中查找对应的依赖属性是否在本地有值。 EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index); if (effectiveValue.PropertyIndex != 0) {//如果获取的元素的PropertyIndex不等于0,说明本地存在这个属性的值 effectiveValue.Value = value; } else {//本地不存在,但是我们进行了Setvalue的调用,所以需要在本地创建一个该属性的数据结构 effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index,Value = value }; _effectiveValues.Add(effectiveValue); } } public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty,value); } } } internal struct EffectiveValueEntry { internal int PropertyIndex { get; set; } internal object Value { get; set; } }
class MyDependencyProperty { internal static Dictionary<object,MyDependencyProperty>(); internal string Name; internal object Value; internal object HashCode; private static int globalIndex = 0;//所有的属性值的index将都会大于0 internal int Index; private MyDependencyProperty(string name,object defaultValue) { this.Name = name; this.Value = defaultValue; this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode(); } public static MyDependencyProperty Register(string name,defaultValue); globalIndex++; dp.Index = globalIndex; RegisteredDps.Add(dp.HashCode,dp); return dp; } }
这样我们就解决了,当一个对象依赖属性设置后,其他该类对象的依赖属性也被设置的情况。 在DependencyObject加入了一个_effectiveValues,就是把所有修改过的DP都保存在EffectiveValueEntry里,这样,就可以达到只保存修改的属性,未修改过的属性仍然读取DP的默认值,优化了属性的储存。
但随着实际的使用,又一个问题暴露出来了。使用继承,子类可以重写父类的字段,换句话说,这个默认值应该是可以子类化的。那么怎么处理,子类重新注册一个DP,传入新的默认值?
其实很简单,我们在MyDependencyProperty中添加一个链表(List),存储这个依赖属性被定义它的类的子类对象重写时候的值。这样,因为每个依赖属性其实在clr加载的时候就已经伴随着这个类对象被构造或者调用,这个依赖属性作为一个静态数据结构就已经被在内存中创建了一份,然后其中保存着这个属性的默认值,当一个具体的对象修改自己本身的这个属性的时候,就在本地存储一个这个属性。(这里的本地是指在当前类的实例内存空间中存储一份属性的实例)。当我们需要访问这个属性的时候就先检查本地对象中是否有这个属性,没有就去访问这个静态的内存,那里面是属于所有这个类对象的依赖属性的值。
对于一个子类来说,他可以更新这个父类中关于这个依赖属性的初始值。
如下来做,在我们原有个MyDependencyProperty中添加一个list,这个list不是静态的,但我们使用这个类的子类,那么这个类的子类应该可以去重写这个依赖属性的默认值,当重写的时候,我们首先找到它从父类继承的MyDependencyProperty对象,然后在list中加入当前子类的类型和这个类型依赖属性的默认值。
当使用子类去获取依赖属性的值的时候,首先检查本地有没有设置好的属性值,没有就去MyDependencyProperty中找,找的时候首先检查list中有没有符合要求的,没有再去取默认值。
public class DependencyObject { private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>(); public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name",typeof(DependencyObject),"Name"); public object GetValue(DependencyProperty dp) {//获取一个依赖属性的值,首先看本地是否有设定的值 EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index); if (effectiveValue.PropertyIndex != 0) { return effectiveValue.Value; } else {//没有本地设定值再去DependencyProperty中查找 PropertyMetadata Metadata; Metadata = DependencyProperty.RegisteredDps[dp.HashCode].GetMetadata(this.GetType()); return Metadata.Value; } } public void SetValue(DependencyProperty dp,object value) { EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index); if (effectiveValue.PropertyIndex != 0) { effectiveValue.Value = value; } else { effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index,Value = value }; _effectiveValues.Add(effectiveValue); } } public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty,value); } } } public class SubDependencyObject : DependencyObject { static SubDependencyObject() {//子类重写这个依赖属性的默认值 NameProperty.OverrideMetadata(typeof(SubDependencyObject),new PropertyMetadata("SubName")); } } public class DependencyProperty { private static int globalIndex = 0; internal static Dictionary<object,DependencyProperty> RegisteredDps = new Dictionary<object,DependencyProperty>(); internal string Name; internal object Value; internal int Index; internal object HashCode; //_defaultMetadata表示这个依赖属性在定义类中的值 private PropertyMetadata _defaultMetadata; //这个List存储所有的依赖属性可能值(子类重写的值) private List<PropertyMetadata> _MetadataMap = new List<PropertyMetadata>(); private DependencyProperty(string name,object defaultValue) { this.Name = name; this.Value = defaultValue; this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode(); //只有父类才会构造这个DependencyProperty对象,将父类的值作为默认值 PropertyMetadata Metadata = new PropertyMetadata(defaultValue) { Type = ownerType }; _MetadataMap.Add(Metadata); _defaultMetadata = Metadata; } public static DependencyProperty Register(string name,object defaultValue) { DependencyProperty dp = new DependencyProperty(name,defaultValue); globalIndex++; dp.Index = globalIndex; RegisteredDps.Add(dp.HashCode,dp); return dp; } /// <summary> /// 子类重写这个依赖属性 /// </summary> public void OverrideMetadata(Type forType,PropertyMetadata Metadata) { Metadata.Type = forType; _MetadataMap.Add(Metadata);//将重写的值加入这个依赖属性的数据结构 } public PropertyMetadata GetMetadata(Type type) { //判断list中是否有当前类的依赖属性值 PropertyMetadata medatata = _MetadataMap.FirstOrDefault((i) => i.Type == type) ?? _MetadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type)); if (medatata == null) {//没有就将默认值返回(子类未重写,返回父类定义的默认值) medatata = _defaultMetadata; } return medatata;//子类重写就返回子类的值。 } } internal struct EffectiveValueEntry { internal int PropertyIndex { get; set; } internal object Value { get; set; } } public class PropertyMetadata { public Type Type { get; set; } public object Value { get; set; } //存储子类类型以及它对于的依赖属性的默认值 public PropertyMetadata(object defaultValue) { this.Value = defaultValue; } }
当然上述的依赖属性方式还是有一定的问题,比如说Class2继承与Class1,Class1包含一个依赖属性dp1,他对于设置的默认值是value1,而class1继承与class0,class中构造了这个依赖属性,并设置默认值为value0,那么用我们上述的方式获取到的Class2的依赖属性dp1的值将是value1.
public class SubDependencyObject1 : SubDependencyObject { } static void Main(string[] args) { SubDependencyObject1 subObj = new SubDependencyObject1(); Console.WriteLine(subObj.Name); }
这里输出的是Name而不是SubName。问题出在了这里:
public PropertyMetadata GetMetadata(Type type) { //判断list中是否有当前类的依赖属性值 PropertyMetadata medatata = _MetadataMap.FirstOrDefault((i) => i.Type == type) ?? _MetadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type)); if (medatata == null) {//没有就将默认值就应该继续搜索离他最近的父类是否有默认值,返回离他最近的父类的值。 medatata = _defaultMetadata; } return medatata;//子类重写就返回子类的值。 }
我们为程序添加一个数据结构来辅助我们完成这个功能,这个数据结构记录我们所使用到的类以及他的父类,并且父类的id永远小于子类。
public class DependencyObjectType { private DependencyObjectType _baseDType; private static int DTypeCount = 0; private static Hashtable DTypeFromCLRType = new Hashtable(); public static DependencyObjectType FromSystemTypeRecursive(Type systemType) { DependencyObjectType type = (DependencyObjectType)DTypeFromCLRType[systemType]; if (type == null) { type = new DependencyObjectType(); DTypeFromCLRType[systemType] = type; if (systemType != typeof(DependencyObject)) {//父类比子类的id要小,因为查找的时候首先建立的是子类的DependencyObjectType //这是一个递归的构造过程。从最上层的DependencyObject类一直都当前类 type._baseDType = FromSystemTypeRecursive(systemType.BaseType); } type._id = DTypeCount++; } return type; } public int Id { get { return this._id; } } public DependencyObjectType BaseType { get { return this._baseDType; } } }
这个hash表会存储我们所使用的所有类。当我们调用FromSystemTypeRecursive的时候。
我们在存储重写父类依赖属性数据的时候要讲原来的type数据转化成int型的id,方便我们判断。
public class PropertyMetadata { public int TypeId { get; set; } public object Value { get; set; } //存储子类类型以及它对于的依赖属性的默认值 public PropertyMetadata(object defaultValue) { this.Value = defaultValue; } }
在初次父类构造依赖属性时以及子类重写属性调用OverrideMetadata时,都要记录上类的id号。
DependencyObjectType dt =DependencyObjectType.FromSystemTypeRecursive(ownerType); PropertyMetadata Metadata = new PropertyMetadata(defaultValue) { TypeId = dt.Id }; _MetadataMap.Add(Metadata);
public PropertyMetadata GetMetadata(Type type) { DependencyObjectType dependencyObjectType =DependencyObjectType.FromSystemTypeRecursive(type); if (dependencyObjectType != null) { int num2; object obj2; int index = this._MetadataMap.Count - 1; if (index < 0) {//如果根本就没有子类重写属性,返回默认值 return this._defaultMetadata; } if (index == 0)//只有一个重写 { num2 = _MetadataMap[index].TypeId; obj2 = _MetadataMap[index]; while (dependencyObjectType.Id > num2) {//说明当前_MetadataMap中存储的值可能是当前类的父类写的 //所以用当前类的父类去和这个值进行对比 dependencyObjectType = dependencyObjectType.BaseType; } if (num2 == dependencyObjectType.Id) { return (PropertyMetadata)obj2; } } else if (dependencyObjectType.Id != 0) {//当有多个子类重写这个依赖属性,遍历所有值,从最后一个值开始 //因为最后一个值的设置一般是最上层的类 do { num2 = _MetadataMap[index].TypeId; obj2 = _MetadataMap[index]; index--; while ((dependencyObjectType.Id < num2) && (index >= 0)) {//当前类的id比存储次属性的类小,直接与下一个值进行对比 num2 = _MetadataMap[index].TypeId; obj2 = _MetadataMap[index]; index--; } while (dependencyObjectType.Id > num2) {//说明当前_MetadataMap中存储的值可能是当前类的父类写的 //所以用当前类的父类去和这个值进行对比 dependencyObjectType = dependencyObjectType.BaseType; } if (num2 == dependencyObjectType.Id) { return (PropertyMetadata)obj2; } } while (index >= 0); } } return this._defaultMetadata; }
现在重新执行
static void Main(string[] args) { SubDependencyObject1 subObj = new SubDependencyObject1(); Console.WriteLine(subObj.Name); Console.ReadKey(); }
结果为:SubName
这样我们就解决了,当一个对象依赖属性设置后,其他该类对象的依赖属性也被设置的情况。 在DependencyObject加入了一个_effectiveValues,就是把所有修改过的DP都保存在EffectiveValueEntry里,这样,就可以达到只保存修改的属性,未修改过的属性仍然读取DP的默认值,优化了属性的储存。
但随着实际的使用,又一个问题暴露出来了。使用继承,子类可以重写父类的字段,换句话说,这个默认值应该是可以子类化的。那么怎么处理,子类重新注册一个DP,传入新的默认值?
其实很简单,我们在MyDependencyProperty中添加一个链表(List),存储这个依赖属性被定义它的类的子类对象重写时候的值。这样,因为每个依赖属性其实在clr加载的时候就已经伴随着这个类对象被构造或者调用,这个依赖属性作为一个静态数据结构就已经被在内存中创建了一份,然后其中保存着这个属性的默认值,当一个具体的对象修改自己本身的这个属性的时候,就在本地存储一个这个属性。(这里的本地是指在当前类的实例内存空间中存储一份属性的实例)。当我们需要访问这个属性的时候就先检查本地对象中是否有这个属性,没有就去访问这个静态的内存,那里面是属于所有这个类对象的依赖属性的值。
对于一个子类来说,他可以更新这个父类中关于这个依赖属性的初始值。
如下来做,在我们原有个MyDependencyProperty中添加一个list,这个list不是静态的,但我们使用这个类的子类,那么这个类的子类应该可以去重写这个依赖属性的默认值,当重写的时候,我们首先找到它从父类继承的MyDependencyProperty对象,然后在list中加入当前子类的类型和这个类型依赖属性的默认值。
当使用子类去获取依赖属性的值的时候,首先检查本地有没有设置好的属性值,没有就去MyDependencyProperty中找,找的时候首先检查list中有没有符合要求的,没有再去取默认值。