首先,我們分析一下依賴屬性產生的一個原因:
由於一個類型的依賴屬性大多數情況下都保持默認值,例如幾百個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, Type ownerType, 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", typeof(string), typeof(MyDependencyObject), 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; }
}
當然依賴屬性類中也需要添加屬性的index。
class MyDependencyProperty
{
internal static Dictionary<object, MyDependencyProperty> RegisteredDps = new 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, 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, Type ownerType, object defaultValue)
{
MyDependencyProperty dp = new MyDependencyProperty(name, propertyType, ownerType, 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(string), 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, Type propertyName, Type ownerType, 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, Type propertyType, Type ownerType, object defaultValue)
{
DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, 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);
下面就輪到重新檢索了,改寫GetMetadata函數:
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中有沒有符合要求的,沒有再去取默認值。