日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

剖析WPF依赖属性

發布時間:2023/12/4 asp.net 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 剖析WPF依赖属性 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? ??這節來講一下WPF中的依賴屬性 (Dependency Property)

【了解屬性和字段】

????我們知道,屬性是面向對象語言中用來封裝字段的外衣,它像是字段對外界的橋梁,我們可以通過屬性來驗證數據的合法性或控制對外的訪問性等等。每個屬性的背后都有其對應的一個字段做支撐,就算是自動屬性,在編譯時系統也會創建其字段,只不過自動屬性是微軟給我們的語法糖罷了。在C#中,屬性最后是會編譯成兩個方法:get_屬性名set_屬性名(如果是只讀屬性,則沒有set方法,反之沒有get方法)。

????編譯成方法,屬性就不會占用太多空間,因為方法存在于內存公共的方法區,每個實例的創建不過是多一個指向該方法的指針。但是字段不一樣,每個實例創建的創建,都會在內存中開辟對應的空間來存放字段,一個類中的字段越多,它在內存中占用的空間就越大,理解了這個理論,下面我們來正式說明什么是依賴屬性,為什么要有依賴屬性。

????

【什么是依賴屬性】

? ??我們使用一個控件,可以看到這個控件有很多的屬性,有屬性就有字段的內存開銷,但實際上對于一個控件,我們大多數只會使用其部分常用屬性,比如Button我們最常使用Content,Height等屬性,那些不經常使用的屬性相當于白白占用著內存。當我們寫一個復雜的XAML頁面,涉及到很多控件的使用時,這種浪費內存的現象就很嚴重。

????對此,微軟在WPF中引入了依賴屬性(Dependency Property),依賴屬性允許沒有自己的字段,可以通過Binding綁定到其它對象的屬性或者說數據源上,從而獲得值,這種依賴在其它對象上的屬性,就是依賴屬性,當明確了它的功能,我想大家就不會對依賴二字產生疑惑了,依賴屬性沒有自己的字段,只在使用時通過Binding從別的對象身上獲取,給自己臨時創建內存空間,這樣不使用就不會有多余內存消耗。

????包含依賴屬性的對象稱為依賴對象(Dependency Object),這種對象需要繼承DependencyObject這個基類,實際上,WPF中的控件,都繼承了DependencyObject這個類,控件中的大部分屬性都是依賴屬性,這樣我們才能通過Binding去綁定值(不熟悉Binding的同學可以參見前文Binding(一):數據綁定系列),才不會有內存浪費現象的發生。

【從代碼中學習依賴屬性】

????下面我們通過代碼來學習一下如何聲明并使用依賴屬性,請先看我寫好的一段代碼:

public class Pikachu : DependencyObject {public static readonly DependencyProperty PikachuNameProperty =DependencyProperty.Register("PikachuName",typeof(string),typeof(Pikachu)); }

????上文說到,使用依賴屬性必須要繼承DependencyObject類,另外,聲明

依賴屬性,需要使用public static readonly三個修飾符修飾,實例依賴屬性也不是通過new操作符,而是通過DependencyProperty的Register方法來獲取

????依賴對象的名字,有個約定,就是以Property為后綴,在C#中有很多命名約定,比如接口用I做前綴,特性用Attribute做后綴等等,這樣做都是為了有個良好的命名規范,做到見名知意。

??? Register方法有三個重載,此處用的是其三個參數的重載,它還有四個參數和五個參數的重載。

  • 第一參數是指定依賴屬性的包裝器名稱是什么(包裝器就是用來包裝依賴屬性的,通過一個屬性來包裝依賴屬性供外部使用,具體下文會講,此處先做了解)

  • 第二個參數是指定依賴屬性要存儲的值的類型是什么

  • 第三個參數是指定依賴屬性屬于哪個類的,或者說是為哪個類定義依賴屬性

  • 其它重載中第四個參數是指定依賴屬性的源數據,用于提供給調用者此依賴屬性的信息

  • 其它重載中第五個參數是自定義的依賴屬性生成時的驗證回調

????聲明了依賴屬性,但是如何給依賴屬性賦值呢,這就要用到DependencyObject基類中的方法了,我們使用其中的SetValue方法和GetValue方法來操作依賴屬性的值,請看下面改動后的代碼:

public class Pikachu : DependencyObject {public string PikachuName {get => (string)GetValue(PikachuNameProperty); set => SetValue(PikachuNameProperty, value); }public static readonly DependencyProperty PikachuNameProperty =DependencyProperty.Register("PikachuName", typeof(string), typeof(Pikachu)); }

???上述代碼,就是一個比較完善的聲明依賴屬性并通過包裝器將依賴屬性暴露出去的例子,屬性PikachuName就是依賴屬性的包裝器,在get塊中通過GetValue方法傳入依賴屬性的名字獲取依賴屬性的值,在Set塊中通過SetValue方法,給依賴屬性賦值,對依賴屬性的這層包裝,使得我們在外部操作依賴屬性變得簡單,這也是為什么我們在正常使用中感覺不到依賴屬性的存在,因為字段也好,依賴屬性也好,我們在外部看到的操作的都是它的屬性。

????下面通過一個實例展示一下依賴屬性的使用:

????前臺代碼是一個名為btn_show的Button控件,后臺代碼如下:

public MainWindowBase() {InitializeComponent();this.DataContext = this;Data = "我是皮卡丘";Pikachu pikachu = new Pikachu(); //使用Binding操作類將皮卡丘對象的皮卡丘名字依賴屬性關聯到Data上BindingOperations.SetBinding(pikachu,Pikachu.PikachuNameProperty, new Binding("Data") { Source = this });//將按鈕的Content依賴屬性綁定到皮卡丘的皮卡丘名字包裝器上btn_show.SetBinding(Button.ContentProperty, new Binding(nameof(pikachu.PikachuName)) { Source = pikachu }); }

????這個例子的邏輯是有一個名為Data的屬性作為數據源,先將皮卡丘對象的依賴屬性綁定到Data數據源上,再將Button的Content依賴屬性綁定到皮卡丘對象的依賴屬性包裝器上,這就形成了一個Binding鏈,運行效果如下:

????整個過程中,只有Data屬性是有字段在背后支撐的,它存儲了“我是皮卡丘”這個數據,皮卡丘對象和Button對象都是依賴屬性,不占內存空間,它們之間使用Binding關聯,形成數據通道,這樣就實現了一塊內存,供給多處使用。按照之前的編程模式,需要皮卡丘和Button各自開辟一段空間存儲Data來的數據,現在由三塊內存節省為一塊內存,這就是依賴屬性對于節省內存的效果。

【從源碼分析依賴屬性】

????下面我們來分析一下,為什么依賴屬性不是用new實例,而是要注冊,以及Get/SetValue的操作依賴屬性值的原理。

????我們先從Register方法看起:

??? Register的三個和四個參數的重載都指向了五個參數的重載,我們主要看一下這五參數重載的方法里邊都有什么。方法體里邊,前幾行實際上是一些驗證代碼,當參數有誤時,會拋出異常。緊接著的是一個返回依賴屬性對象的RegisterCommon方法,從名字和返回值來看這就是最核心的方法了,我們接著跟進去看:

????代碼內部第一行使用FromNameKey生成了一個key對象,這個FromNameKey是Dependency類的一個內部類,它構造器需要傳入的包裝器名稱和依賴對象所在的類的Type, ?? 這個類及構造器代碼如下:

????構造器第三行代碼比較重要,我們可以看到,這個類通過傳入的參數兩者異或生成了一個hashcode,經過這個異或運算,那就保證了同一個類,同樣的包裝器名稱生成的hashcode是一樣的。

????同時這個類重寫了GetHashCode方法,就是把異或生成的hashcode返回出去了。

????了解了這個類,我們再回到RegisterCommon類中,接著往下看,下面是一個線程同步塊:

? ? 這個代碼塊里邊,出現了一個PropertyFromName參數,看樣子是個集合,我們找到這個屬性的定義處,發現它是個全局的HashTable:

????那這個代碼塊的意思就明了了,目的就是判斷生成的Key是否已存在,如果存在,就拋異常,從這里就控制了,在類內部定義兩個相同包裝器名稱的依賴屬性是不允許的,實際上也必須是這樣,同一個類中,屬性肯定是不能同名的,依賴屬性也是如此,那我們從此處還能獲得一個信息,就是PropertyFromName肯定和我們要生成的依賴屬性有很大的關系,具體我們繼續往下看代碼:

????如果沒有傳入依賴屬性的源數據,系統會生成默認的源數據,在往下看是一些校驗邏輯,具體內容此處就不分析,有興趣的可以自己點進去看,緊接著就到代碼核心了:

????經過層層把關,依賴屬性終于new出來了,new出來后,下面我們又看到PropertyFromName的影子了:

????原來PropertyFromName是存儲依賴屬性的一個集合,所有new出來的依賴對象都存儲在這里,它的hashcode就是之前通過FromNameKey類異或出的。

????最后,通過return,返回了這個依賴屬性 ,至此,依賴屬性的整個創建過程解析完畢。

????我們再來了解一下依賴屬性的值的讀取:

? ? 先看GetValue方法:

????前幾句代碼還是校驗,核心代碼是最后一句,此處涉及到了依賴屬性的GlobalIndex屬性,這個屬性是系統經過一系列算法得出的,具有唯一性,我們看到,這個GlobalIndex傳入了名為LookupEntry方法中,Entry是入口的意思,從方法名上看,我們能得知,是根據GlobalIndex找到了一個訪問入口,實際上,這個入口就是依賴屬性值的訪問入口。

????我們進入GetValueEntry方法中查看,會找到一個名為_effectiveValues的屬性,這是一個EffectiveValueEntry類型的數組,原來,依賴屬性所有的值都存放在這個數組中,根據依賴屬性唯一的GlobalIndex,我們就能從這個數組中找到依賴屬性的值。

????再來看SetValue方法:

????其實明白了GetValue,SetValue也就很好理解了,道理都是一樣的,根據依賴屬性的GlobalIndex值獲取到入口,更新上新值,我們進入SetValueCommon方法中看,代碼比較繁瑣,實際上的流程有三塊:

  • 判斷值是不是DependencyProperty.UnsetValue,如果是,則清除依賴屬性的值,所以我們要想對依賴屬性設置空值,不要用null,要用DependencyProperty.UnsetValue

  • 判斷能否找到入口,如果沒有入口,則新建一個入口對象,將值放進去,有入口則更新值

  • 最后,通過UpdateEffectiveValue方法對依賴屬性的值做一些處理

????至此依賴屬性的讀取流程解析完畢。

??

????本節到此結束...

總結

以上是生活随笔為你收集整理的剖析WPF依赖属性的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。