[轉]WPF的依賴屬性是怎麼節約內存的

WPF升級了CLR的屬性系統,加入了依賴屬性和附加屬性。依賴屬性的使用有不少好處,其中有兩點是我認爲最爲亮眼的:html

1)節省內存的開銷;數組

2)屬性值能夠經過Binding依賴於其它對象上,這就使得個人數據源一變更所有依賴於此數據源的依賴屬性所有進行更新。ide

第二點開發過WPF或者SilverLight應用程序都能無比暢快地感覺它帶來的好處,而在節省內存這個亮點上咱們也行未能深入地感覺它帶來的心理上的爽快,本人試着簡單地說明依賴屬性究竟是怎麼樣爲咱們節省內存的。優化

咱們先來看看傳統的CLR屬性,先來定義我的的類Person,簡單點,只包含了Name和Coutry屬性,Country默認是China。ui

 public class Personthis

    {spa

        public Person()htm

        {對象

            Country = "China";繼承

        }

        public string Name { get; set; }

        public string Country { get; set; }

 

    }

 

若是咱們如今須要造10000箇中國人,太簡單了,在一個循環裏實例化10000個Person就好了。若是你根本不關心程序佔用內存的消耗你固然不會心痛,不然你就會喊坑爹了,由於這10000箇中國人的Country都是China,但內存必須爲每一個China開闢空間來存放,這實在暴殄天物啊!好吧,到這裏你應該知道依賴屬性靠節省內存這個亮點均可以閃亮登場了,雖然咱們最爽的仍是前面說的第一點的一變全變的功能。

談依賴屬性DependencyProperty就繞不過要談談依賴對象DependencyObject,這還得從Dependency提供的兩個方法提及,GetValue和SetValue。

public class DependencyObject :DispatcherObject

 

{

 

public object GetValue(DependencyProperty dp)

 

{

 

 

 

}

 

public void SetValue(DependencyProperty dp,object value)

 

{

 

}

 

}

原來DependencyProperty自己不提供獲取和設置依賴屬性值的操做,而是由DependencyObject來負責。DependencyObject是使用GetValue和SetValue方法經過DependencyProperty實例實現對屬性值的讀取和保存。注意這裏所說的讀取和保存的屬性值其實就是DependencyProperty對應的屬性值,也就是SetValue方法第二個參數object類型的value,它和DependencyProperty實例對象自己是不一樣的東西。若是還不能很好理解,不要緊,繼續往下看就明白了,咱們下面就升級下Person的Country屬性,使之成爲真正的依賴屬性。

 public class Person : DependencyObject

    {

        /// <summary>

        /// 依賴屬性

        /// </summary>

        public static readonly DependencyProperty CountryProperty = DependencyProperty.Register("Country", typeof(string), typeof(Person), new PropertyMetadata("China"), new ValidateValueCallback(CountryValidateValueCallback));

 

        /// <summary>

        /// 依賴屬性的CLR屬性包裝器

        /// </summary>

        public string Country

        {

            get { return (string)GetValue(CountryProperty); }

            set { SetValue(CountryProperty, value); }

        }

 

        /// <summary>

        /// 屬性值驗證

        /// </summary>

        /// <param name="value"></param>

        /// <returns></returns>

        public static bool CountryValidateValueCallback(object value)

        {

            string  country = (string)value;

            if (country.Equals("Japan")) return false;

            return true;

        }

 

        /// <summary>

        /// Name是一個CLR屬性

        /// </summary>

        public string Name { get; set; }

    }

上面建立的一個依賴屬性CountryProperty是用到DependencyProperty.Register最徹底的參數的重載方法,關於這個方法裏面各個參數的詳情不是本文討論的重點,若是你須要詳細瞭解,能夠閱讀Clingingboy寫的關於依賴屬性的文章。DependencyProperty.Register的一個參數」Country」用來指明哪一個CLR屬性做爲這個依賴屬性的包裝器,另外還須要使用這個它的HashCode,這點後面會講到。第二個參數「typeof(string)」指明此依賴屬性存儲的的什麼類型的值。第三個參數「typeof(Person)」用來指明此依賴屬性宿主是什麼類型,也須要使用它的HashCode。第四個參數「new PropertyMetadata("China")」能夠指定依賴屬性讀取值的默認值,這裏默認是「China」,第五個參數「new ValidateValueCallback(CountryValidateValueCallback)」,它指定驗證值的方法,好吧,咱們這個驗證值的方法就把Japan先排除吧,凡是Country賦值爲Japan就會拋出異常。

這裏定義的CountryProperty是一個static,咱們知道靜態對象裏面的值都是一變全變的,也就是我在一處修改了靜態對象,全部引用這個靜態對象的地方都會改變,這說明CountryProperty不適合來保存value這個屬性值,不然假如我實例化10000個Person,這10000個Person都共用一個CountryProperty,到底用來保存哪一個Person對象的Country呢?

上面實例化一個DependencyProperty沒有使用new關鍵字,而是使用DependencyProperty.Register這個靜態方法,這個靜態方法大有乾坤,下面咱們重點分析下這個靜態方法。

咱們先看看DependencyProperty類中DependencyProperty.Register的源碼,這裏爲了閱讀方便將干擾咱們閱讀的部分代碼去掉了:

 public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)

        {

            RegisterParameterValidation(name, propertyType, ownerType);

            DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);

            return property;

        }

RegisterParameterValidation(name, propertyType, ownerType)方法驗證第一個、第二個和第三個參數是否爲null,任何一個爲null都將拋出異常,說明這三個參數在調用Register方法是必傳的。

DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback),這行告訴咱們內部又使用了RegisterCommon方法來實例化DependencyProperty對象。咱們在來看看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));

                }

            }

 

            // Establish default metadata for all types, if none is provided

            if (defaultMetadata == null)

            {

                defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType);

            }

                       // Create property

            DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);

 

                       // Map owner type to this property

            // Build key

            lock (Synchronized)

            {

                PropertyFromName[key] = dp;

            }

 

 

            return dp;

        }

if (defaultMetadata == null)

 {

 defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType);

 }

 

從這段代碼中咱們能夠知道DependencyProperty.Register的第四個參數傳爲null的時候,會建立一個默認的元數據。

PropertyFromName[key] = dp;這句代碼告訴咱們建立出來的DependencyProperty對象是保持在名爲PropertyFromName的全局的Hashtable中的。

private static Hashtable PropertyFromName = new Hashtable();

那麼這個key是怎麼來的呢,回到源碼的第一句:FromNameKey key = new FromNameKey(name, ownerType),能夠知道PropertyFromName中的key對象類型是FromNameKey,咱們知道判斷一個Hashtable中元素的key是否相同判斷的是key的HashCode是否相同,因此咱們下一步天然是須要看看FromNameKey的GetHashCode返回的什麼:

 private class FromNameKey

        {

            public FromNameKey(string name, Type ownerType)

            {

                _name = name;

                _ownerType = ownerType;

 

                _hashCode = _name.GetHashCode() ^ _ownerType.GetHashCode();

            }

 

            public void UpdateNameKey(Type ownerType)

            {

                _ownerType = ownerType;

 

                _hashCode = _name.GetHashCode() ^ _ownerType.GetHashCode();

            }

 

            public override int GetHashCode()

            {

                return _hashCode;

            }

}

如今一目瞭然了,咱們前面提到了DependencyProperty.Register的第一個參數「Country」和第三個參數typeof(Person)須要用到它們的HachCode,這裏的FromNameKey的HashCode值就是第一個參數的HashCode和第三個參數typeof(Person)的HashCode進行^運算獲得的。從而咱們能夠推知,一個類型的同一個Dependency在全局的PropertyFromName裏面只會保存一個實例對象(由於Hashtable的鍵值對裏面不容許存在相同的鍵)。

經過以上分析,咱們能夠總結下DependencyProperty.Register的做用:

1)將一個DependencyProperty對象存儲在一個全局的Hashtable中(PropertyFromName),而這個Hashtable存儲的對象的Key由依賴屬性對象對應的CLR屬性包裝器名稱和依賴屬性對象寄存的類型DHashCode決定。

2)DependencyProperty對象保存了一個屬性元數據的默認值;

3)返回一個DependencyProperty對象實例。

DependencyProperty對象只是保存了一個默認值,那麼咱們調用DependencyObject的SetValue(DependencyProperty dp,object value)方法保存屬性值的時候,這裏面的第二個參數value到底保存到哪裏去了呢?這就須要來閱讀下DependencyObject類的源碼了,閱讀源碼咱們發現DependencyObject類有一個私有數組變量:

private EffectiveValueEntry[] _effectiveValues;

當咱們調用DependencyObject的GetValue(DependencyProperty dp)方法的時候,會根據DependencyProperty 對象的GlobalIndex屬性判斷EffectiveValueEntry[] 是否包含這個依賴屬性對象的屬性值,若是沒有就返回依賴屬性的默認值。理解這點很關鍵,這是依賴屬性節省內存的關鍵之一,由於一個類裏面的依賴屬性對象的靜態的,也就是全部的實例化對象都是公用這個依賴屬性對象,回到咱們開篇的話題,當你實例化10000個Person的時候,若是沒有調用Person的SetValue方法,那麼讀取這10000個Person的Country屬性時,都是從一個依賴屬性對象的CountryProperty的默認值讀取的,這個時候內存中只用一個地址存放「China」就能夠了,大大節省了內存的開銷。

當咱們調用DependencyObject的SetValue(DependencyProperty dp,object value)方法時,就會將這個值保存在EffectiveValueEntry[]某個EffectiveValueEntry類型的元素上。

在WPF中大部分UI控件都有很長的繼承體系,一個控件經過繼承而來的屬性就有一籮筐了。另外將依賴屬性定義在控件的父類裏面,那麼這個父類全部的子類對象都會共享依賴屬性對象,可見依賴屬性的使用大大下降了控件對內存的消耗。

但願這篇文章對於你理解WPF中的依賴屬性節省內存的機制有所幫助。筆者水平有限,若是說的不對,請高手斧正。

後記:成文後,我想起了string類型在.NET中採用享元模式,也就是說string a = "China"和string b = 「China」兩個變量a和b其實指向同一個地址的。在本文中的Person類中CountryProperty存儲的數據類型是一個string,由於.NET對string的特殊優化,因此定義一個存儲string類型DependencyProperty來講明節省內存不太合適,假如我建立10000個Person對象而言,那麼採用依賴屬性的優點就是節省了存放指向「China」地址的引用的地址——這麼說來仍是有節省內存的優點,只不過若是用其餘類型如List<Int>類型來寫這個文章更合適些了。

 

轉載來源 :https://www.aliyun.com/jiaocheng/619456.html

相關文章
相關標籤/搜索