WPF:依賴屬性

前言:
在使用WPF的時候,總會有一個疑問,依賴屬性跟普通的類屬性有什麼區別,微軟要在WPF引入它想要解決什麼問題?如果不解除這個疑惑,在編程的時候心裏總會不踏實。因此我在網上找了一些資料,終於弄懂了它的由來和機制,特意和大家分享,如有不足,請各位指正!
依賴屬性的由來:
在WinForm時代,每個控件類(如TextBox)都會包含許多屬性,但是真正用到的少之又少(如text),其他屬性就會白白耗費內存資源。那麼問題來了,如果只生成一個控件對象,“無用”的屬性對性能影響不大,但是當你在一個窗體實例化幾十個控件對象的時候,那內存消耗就很可觀了。所以,我們應該引入一個機制,在實例化對象的時候,按需“給”它屬性,這樣子就不會讓“無用”的屬性白白佔用內存資源,因此依賴屬性誕生了。
依賴屬性的定義:
先比較淺顯地講講依賴屬性的定義,即本身可以沒有值,而依賴於其他數據源而取得值的屬性。
自定義依賴屬性:
在深入瞭解依賴屬性之前,很有必要弄清楚依賴屬性是如何敲出來的。假設有一個Person類,現在我要定義一個依賴屬性NameProperty,代碼如下:

    //依賴屬性必須在依賴對象DependencyObject或其子類中定義    class Person : DependencyObject    {        //CLR屬性包裝器,使得依賴屬性NameProperty在外部能夠像普通屬性那樣使用        public string Name        {            get { return (string)GetValue(NameProperty); }            set { SetValue(NameProperty, value); }        }         //依賴屬性必須爲static readonly,後續講解        public static readonly DependencyProperty NameProperty =            DependencyProperty.Register("Name", typeof(string), typeof(Person), new PropertyMetadata("DefaultName"));    }代碼分析:

1.依賴屬性必須在依賴對象(DependencyObject)或其子類中定義,因此你能夠發現絕大多數WPF控件都是繼承自DependencyObject的;
2.CLR屬性Name的作用之一是爲了讓外界在使用依賴屬性時,像使用普通屬性一樣;
3.依賴屬性約定以Property結尾;
4.依賴屬性必須聲明爲static readonly,原因後續講解;
5.生成依賴屬性需要Register方法而不是new,方法的參數依次是CLR屬性名、依賴屬性對應的類型(注意:所有依賴屬性真正的類型是DependencyProperty,這裏的意思是依賴屬性所“存儲”的值的類型)、依賴屬性的宿主類、依賴屬性的默認元數據;
6.默認元數據有許多重載,這裏使用的是隻有一個參數的重載類型,傳入的參數是依賴屬性的默認值。
Register方法的內部機制:
很明顯,自定義依賴屬性的關鍵在於Register方法,它的內部機制是:
1.創建DependencyProperty實例dp;
2.根據CLR屬性名和宿主類型名生成哈希碼hashcode,用於唯一標示依賴屬性;
3.在DependencyObject類中有一個全局變量:哈希表PropertyFromName,用於存儲所有依賴屬性的hashcode,因此在這一步檢查是否已經存在相同hashcode,若無則以hashcode和實例dp作爲鍵值對存進哈希表PropertyFromName,否則報錯;
4.最後返回實例dp。
注意,hashcode並不是實例dp的哈希值,而是一個名爲GlobalIndex的int全局變量,由DependencyObject內複雜的算法算出來。
GetValue和SetValue方法的內部機制:
也許這個時候你會有疑問:既然依賴屬性聲明爲static,就代表依賴屬性只有一份拷貝,那麼我定義多個Person類的實例對象時,一旦改變其中一個對象的依賴屬性時,其他的對象豈不是都跟着改變?這時候就需要探討GetValue和SetValue的內部機制了,這裏只說GetValue,因爲弄懂了GetValue後,對SetValue的機制就自然明白了。
在GetValue函數中能夠看到一個EffectiveValueEntry類的實例,它就好像是一個房間入口(Entry),進去後就能獲取想要的值;在每個EffectiveValueEntry的構造函數中有一個名爲PropertyIndex的參數,傳入的值其實就是上面提到的唯一標示不同實例的GlobalIndex,這樣子,每一個實例對象的依賴屬性值所對應的入口(Entry)就不一樣了;而在DependencyObject類中能夠看到 private
 EffectiveValueEntry[] _effectiveValues 這樣一個語句,當某個實例的依賴屬性被讀取時,就會到這個數組檢索它對應的EffectiveValueEntry,如果找不到則代表依賴屬性還沒有被手動賦值(如Name="LiMing"),DependencyObject類的內部算法就會返回依賴屬性的默認值(在Register方法第四個參數中設定)。
這樣就明白了,被static關鍵字所修飾的依賴屬性對象真正作用只是用來檢索真正的屬性值,而不是用來存儲值的;而用於檢索值的是GlobalIndex,因此爲了保證GlobalIndex的穩定性,需要添加readonly關鍵字。
所以,依賴屬性是以犧牲算法來節省內存空間。
結語:
其實依賴屬性的內部機制比上述複雜的多,如果想真正理解其中的原理,還是需要各位自己去鑽研。Thanks for your attention!
 

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