依賴屬性(Dependency Properties)進階(二)

這篇主要介紹依賴屬性的內存使用和存取方面的知識。內容主要來自書籍《深入淺出WPF》。


1. 依賴屬性對內存的使用

首先思考這樣一個問題,TextBox100多個屬性,而常用的也就Text,而在創建Button實例的時候,其他的屬性也是需要佔用內存資源的,是不是很浪費。依賴屬性是依賴在其他屬性上的,而自身是不佔內存空間的,減少了內存資源的開銷。

傳統的.NET開發中,一個對象所佔作用的內存空間在調用new操作符進行實例化的時候就已經決定了,而WPF允許對象在被創建的時候並不包含用於存儲數據的空間(即字段所佔用的空間)、只保留在需要用到數據時能夠獲得默認值、借用其他對象數據或者實時分配空間的能力——這種對象就稱爲依賴對象(Dependency Object)而它這種實時獲取數據的能力則依靠依賴屬性(Dependency Property)來實現。WPF開發中,必須使用依賴對象作爲依賴屬性的宿主,使二者結合起來,才能形成完整的Binding目標被數據所驅動。


2. 依賴屬性存取祕密

依賴屬性使用的兩個步驟:a)在Dependency Object派生類中聲明 public static修飾的DependencyProperty成員變量,並使用DependencyProperty.Register方法(而不是new操作符)獲得DependencyProperty的實例;b)使用DependencyObjectSetValueGetValue方法、藉助DependencyProperty實例來存取值。

1) 註冊方法DependencyProperty.Register()


DependencyProperty類具有這樣一個成員:
privatestatic Hashtable PropertyFromName = new Hashtable();
這個Hashtable就是用來註冊DependencyProperty實例的地方。
在源碼中,所有的Register方法都會調用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));
		}
	}

	//…
	DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
	//…
	lock (Synchronized)
	{
		PropertyFromName[key] = dp;
	}
	//…
	return dp;
}

可以看出,RegisterCommon方法的前4個參數與Register方法一致。
看了RegisterCommon這個方法,便能瞭解DependencyProperty對象的創建和註冊過程:創建一個DependencyProperty實例並用它的CLR屬性名和宿主類型名生成hash code,最後把hash code和DependencyProperty實例作爲Key-Value對存入全局的、名爲PropertyFromName的Hashtable中。這樣,WPF屬性系統通過CLR屬性名和宿主類型名就可以從這個全局的Hashtable中檢索出對應的DependencyProperty實例。最後,生成的DependencyProperty實例被當作返回值交還。

2) 存值方法SetValue()和取值方法GetValue()


GetValue方法代碼如下:
public object GetValue(DependencyProperty dp)
{
	// Do not allow foreign threads access.
	// (This is a noop if this object is not assigned to a Dispatcher.)
	//
	this.VerifyAccess();

	if (dp == null)
	{
		throw new ArgumentNullException("dp");
	}

	// Call Forwarded
	return GetValueEntry(
			LookupEntry(dp.GlobalIndex),
			dp,
			null,
			RequestFlags.FullyResolved).Value;
}

核心內容是return語句,展開爲:
EntryIndex entryIndex=LookupEntry(dp.GlobalIndex);
EffectiveValueEntry valueEntry=GetValueEntry(entryIndex,dp,null,RequestFlags.FullyResolved);
return valueEntry.Value;

這幾句話代碼中屢次出現了Entry這個詞,Entry是“入口”的意思。WPF的依賴屬性系統在存放值的時候會把每個有效值存放在一個“小房間”裏,每個“小房間”都有自己的入口——檢索算法只要找到這個入口、走進入口就能拿到依賴屬性的值。這裏說的“小房間”實際上就是EffectiveValueEntry類的實例。EffectiveValueEntry的所有構造器都包含一個DependencyProperty類型的參數,換句話說,每個EffectiveValueEntry都關聯着一個DependencyProperty。EffectiveValueEntry類具有一個名爲PropertyIndex的屬性,這個屬性的值實際上就是與之關聯的DependencyProperty的GlobalIndex屬性值(就是DependencyProperty實例的哈希值)。
在DependencyObject類的源碼中可以找到這樣一個成員變量:
// The cache of effective values for this DependencyObject
// This is an array sorted by DP.GlobalIndex.  This ordering is
// maintained via an insertion sort algorithm.
private EffectiveValueEntry[] _effectiveValues;

這個數組依每個成員的PropertyIndex屬性值進行排序,對這個數組的操作(如插入、刪除和排序等)由專門的算法來完成。正是這個數組向我們提示了依賴屬性存儲值的祕密——每個DependencyObject實例都自帶一個EffectiveValueEntry類型數組,當某個依賴屬性的值要被讀取時,算法就從這個數組中去檢索值,如果數組中沒有包含這個值,算法就會返回依賴屬性的默認值(這個值由依賴屬性的DefaultMetadata來提供)。
至此,我們瞭解到,那個被static修飾的依賴屬性對象的作用是用來檢索真正的屬性值而不是存儲值;被用來檢索鍵值的實際上是依賴屬性的GlobalIndex屬性(本質上是hash code,而hash code又由其CLR包裝器名和宿主類型名共同決定),爲了保證GlobalIndex屬性值的穩定性,所以聲明時用readonly關鍵詞修飾。
實際工作中,依賴屬性的值除了可能存儲在EffectiveValueEntry數組或其默認值提供外,還可以通過Style,Theme,上層元素繼承等得到,其優先級在前面(依賴屬性基礎)已經說明。
理解GetValue方法後,SetValue方法也不在神祕。賦值的主要流程爲:
  • 檢查值是不是DependencyProperty.UnsetValue,如果是,說明調用者的意圖是清空現有值。此時程序調用ClearValueCommon方法來情況現有值。
  • 檢查EffectiveValueEntry數組中是否已經存在相應依賴屬性的位置,如果有則把舊值改寫爲新值,如果沒有則新建EffectiveValueEntry對象並存儲新值。這樣,只有被用到的值纔會被放進這個列表,藉此,WPF系統用算法(時間)換取了對內存(空間)的節省。
  • 調用UpdateEffectiveValue對新值做一些相應處理。



作者:FoolRabbit
出處:http://blog.csdn.net/rabbitsoft_1987
歡迎任何形式的轉載,未經作者同意,請保留此段聲明!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章