這篇主要介紹依賴屬性的內存使用和存取方面的知識。內容主要來自書籍《深入淺出WPF》。
1. 依賴屬性對內存的使用
首先思考這樣一個問題,TextBox有100多個屬性,而常用的也就Text,而在創建Button實例的時候,其他的屬性也是需要佔用內存資源的,是不是很浪費。依賴屬性是依賴在其他屬性上的,而自身是不佔內存空間的,減少了內存資源的開銷。
傳統的.NET開發中,一個對象所佔作用的內存空間在調用new操作符進行實例化的時候就已經決定了,而WPF允許對象在被創建的時候並不包含用於存儲數據的空間(即字段所佔用的空間)、只保留在需要用到數據時能夠獲得默認值、借用其他對象數據或者實時分配空間的能力——這種對象就稱爲依賴對象(Dependency Object)而它這種實時獲取數據的能力則依靠依賴屬性(Dependency Property)來實現。WPF開發中,必須使用依賴對象作爲依賴屬性的宿主,使二者結合起來,才能形成完整的Binding目標被數據所驅動。
2. 依賴屬性存取祕密
依賴屬性使用的兩個步驟:a)在Dependency Object派生類中聲明 public static修飾的DependencyProperty成員變量,並使用DependencyProperty.Register方法(而不是new操作符)獲得DependencyProperty的實例;b)使用DependencyObject的SetValue和GetValue方法、藉助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
歡迎任何形式的轉載,未經作者同意,請保留此段聲明!