1. 程式人生 > >WPF基礎到企業應用系列8——依賴屬性之“風雲再起”

WPF基礎到企業應用系列8——依賴屬性之“風雲再起”

一. 摘要

  首先聖殿騎士很高興”WPF 基礎到企業應用系列” 能得到大家的關注、支援和認可。看到很多朋友留言希望加快速度的問題,我會盡力的,對你們的熱情關注也表示由衷的感謝。這段時間更新慢的主要原因是因為忙著用TDD還原MONO的框架,同時也因為一直在研究雲端計算,所以就拖拖拉拉一直沒有釋出後面的文章。由於WPF整個系列是自己的一些粗淺心得和微薄經驗,所以不會像寫書那麼面面俱到,如果有不足或者錯誤之處也請大家見諒。在今年之內聖殿騎士會盡量完成”WPF 基礎到企業應用系列”和”雲端計算之旅系列“,誠然,由於本人才識淺薄,所以熱切希望和大家共勉!

  由於依賴屬性是WPF和Silverlight的核心概念,微軟在C\S和B\S平臺上主要精力都放到了WPF和Silverlight技術上,同時Silverlight也是Windows Phone的兩大程式設計模型之一(另外一種是XNA),所以我們花費了大量的時間和篇幅進行論述。在上一篇

WPF基礎到企業應用系列7——深入剖析依賴屬性中,我們首先從依賴屬性基本介紹講起,然後過渡到依賴屬性的優先順序、附加屬性、只讀依賴屬性、依賴屬性元資料、依賴屬性回撥、驗證及強制值、依賴屬性監聽、程式碼段(自動生成) 等相關知識,最後我們模擬一個WPF依賴屬性的實現,由於上篇是根據微軟WPF的BCL原始碼剖析的,所以這篇我們就研究一下.NET的跨平臺版本MONO,看下它是怎麼來實現這個依賴屬性機制。

二. 本文提綱

· 1.摘要

· 2.本文提綱

· 3.兵馬未動、廢話先行

· 4.依賴屬性續前緣

· 5.引入測試驅動開發

· 6.DependencyProperty測試程式碼

· 7.DependencyProperty實現程式碼

· 8.DependencyObject測試程式碼

· 9.DependencyObject實現程式碼

· 10.PropertyMetadata測試程式碼

· 11.PropertyMetadata實現程式碼

· 12.其他協助類測試程式碼

· 13.其他協助類的實現程式碼

· 14.迴歸並統計覆蓋率

· 15.簡單驗證依賴屬性系統

· 16.本文總結

· 17.相關程式碼下載

· 18.系列進度

三. 兵馬未動,廢話先行

在講這篇文章之前,我們先來拉一拉家常,說點題外話,就當進入正餐之前的一些甜點,當然這裡主要針對.NET平臺而言:

1,淺談軟體技術的發展趨勢及定位

  網際網路的普及應用催生了很多技術的發展與更新,如果仔細深究,你會發現軟體技術的發展趨勢將主要體現在以下四個方面:客戶端軟體開發(其中包括客戶端軟體、遊戲、中介軟體和嵌入式開發等)、Web 開發(包括傳統的Web技術、Web遊戲以及一些線上應用)、移動裝置軟體開發(主要涉及到手機等移動裝置)、雲端計算開發(公有云、私有云、混合雲會逐漸界限清晰,雲廠商以及雲平臺也會逐漸整合和成熟起來)。就微軟來說,這四個方面主要如下:

客戶端軟體開發

  目前微軟主要有Win32 應用程式、MFC 應用程式、WinForm應用程式和WPF 應用程式作為開發選擇,目前這四種技術還會共存,因為不同的需求以及不同的人群都有不同的需要。當然WPF藉助於其強大的功能和迅猛的發展速度很快會成為首選,這個是值得肯定的。

Web 開發

  在WEB方面微軟主要有ASP.NET、ASP.NET MVC、Silverlight三種技術,ASP.NET技術已經發展了多年,在未來的很長一段時間內還會是主流,同時結合Silverlight作為區域性和整體應用效果都還很不錯,所以這也是很多企業的首選。ASP.NET MVC在目前來說應用還不是特別廣泛,不過用過之後感覺也還不錯,只是還需要一段時間的適應過程而已。Silverlight在構建區域性應用和整站應用都發揮了不錯的優勢,在Windows Phone中也表現得不錯,所以這個技術將會一直熱下去。

移動裝置軟體開發

  移動裝置方面可謂是現在眾廠商競爭最激烈的市場之一,也是傳統技術和新型技術的主要戰場之一。微軟現在主推的Windows Phone開發主要包括Silverlight和XNA兩種技術,Windows Phone開發逐漸變得和ASP.NET開發一樣簡單,這也是微軟的一個目標。

雲端計算開發

  雲端計算現在基本上成了網際網路的第一大熱門詞,不管是軟體為主導的企業,還是以硬體為主導的企業,都捲入了這場紛爭與革命。微軟的雲平臺——Windows Azure Platform,它是微軟完整的雲端計算平臺,目前包含了如下三大部分(Windows Azure:執行在雲中的作業系統,對於使用者來說是虛擬且透明的,其中提供了Compute(計算),Storage(儲存),以及Manage(管理)這三個主要功能及其底層服務,使用起來相當的便捷。SQL Azure:運行於雲中的一個關係資料庫,和SQL Server 2008類似,但是在功能上還沒有那麼強大。AppFabric:全名是Windows Azure platform AppFabric,提供了訪問控制、服務匯流排等服務,主要用於把基礎應用連線到雲中)。

其實把這四個方面總結起來就是傳說中的微軟“三屏一雲”戰略,從中也可以看出微軟逍遙於天地,縱橫於宇內,嘯傲於世間,雄霸於大地的梟雄戰略!

2,淺談微軟跨平臺與MONO

  在談之前我們先看一下什麼是MONO?MONO專案是由Ximian發起、Miguel de lcaza領導、Novell公司主持的專案。它是一個致力於開創.NET在Linux,FreeBSD,Unix,Mac OS X和Solaris等其他平臺使用的開源工程。它包含了一個C#語言的編譯器,一個CLR的執行時,和一組類庫,並逐漸實現了 ADO.NET、ASP.NET、WinForm、Silverlight(可惜沒有實現強大的WPF),能夠使得開發人員在其他平臺用C#開發程式。

值得看好的地方:

1,跨平臺:開創.NET在Linux,FreeBSD,Unix,Mac OS X和Solaris等其他平臺使用,這是微軟沒有實現的,但是MONO進行了補充,所以值得看好。

2,開源:不論使用什麼技術,大家似乎都希望能夠用開源的產品,一方面是考慮到技術的可控性和可維護性;另一方面則是考慮到安全性,當然在另一個角度也是可以學習到其中的一些技術和思想,所以大家對開源總是報以歡迎的態度。

3,不同的方式實現.NET框架:由於微軟對技術申請了專利,所以MONO不能盲目的模仿,對很多細節都改用自己的方式進行了實現,所以我們也可以學到很多不一樣的實現方式。

4,持續更新:MONO從一開始到現在始終在更新,其中包括bug修復、版本升級、增加新的功能及應用,所以相信它會在不斷的更新中更加完善。

不足之處:

1.模仿但要避免專利:由於是模仿微軟.NET平臺,但因為微軟對程式碼申請了專利,所以MONO只能採用其它實現方式來實現同樣的功能,這樣一來很多地方就會實現得很累贅,效率也會受損。

2.沒有擺脫實驗產品的頭銜:由於它目前的使用比較低,所以資訊反饋和持續改進就做得比較弱,這也是目前功能完善得比較慢的原因之一吧。

3,功能還需要完善:一些主要功能還未實現,如作為Windows平臺最基礎的COM和COM+功能沒有儲存,像MSMQ等訊息佇列,訊息傳送的功能也沒有實現,對ADO.NET、XML等核心功能效率有待提升,對BCL庫程式碼也有很多需要優化的地方,強大的WPF也沒有引入。

4.效率和使用者體驗還有待提升。

與微軟之間的關係

微軟與MONO之間的關係也一直處於不冷不熱的狀態,沒有明確的反對,也沒有明確的支援,究其原因筆者認為主要有以下兩點:

1,微軟帶來最大收益的產品仍舊是Windows作業系統和Office等軟體,微軟在其他領域盈利都沒有這兩大產品來得直接。而.NET作為微軟的強大開發平臺,是不希望落在其他平臺上執行的,這樣就會削弱Windows作業系統和Office等軟體的市場佔有率,所以讓.NET跨平臺對微軟來說是一件捨本求末的事情,這也是微軟不主張.NET運行於其他平臺的主要原因,你想微軟是一個以技術為主導的公司,任何IT市場都會有它的身影,如果想讓.NET跨平臺,那豈不是一件很輕而易舉的事情嗎?

2,由於MONO還沒有成熟,在很多方面都表現得像一個實驗室產品,在根本上沒有對微軟構成威脅,況且在外界質疑.NET是否能跨平臺的時候,還有一個現身的說法,所以微軟也不會明確的反對和支援。

總結

  雖然目前來說MONO喜憂參半,但優點始終要大於缺點,畢竟每一個框架或者產品都是慢慢不斷改進而完善的,更何況開源必將是未來的一個趨勢,所以我們有理由也有信心期待它接下來的發展。

3,談談原始碼研究與TDD

  大家都有一個共識:如果你想研究某個框架或者工具的原始碼,那先必須熟練使用它,熟練之後自然就有一種研究它的衝動,但是往往這個框架或工具比較龐大,很不容易下手,一個很不錯的方法就是使用TDD。我們都知道TDD的基本思想就是在開發功能程式碼之前,先編寫測試程式碼。也就是說在明確要開發某個功能後,首先思考如何對這個功能進行測試,並完成測試程式碼的編寫,然後編寫相關的程式碼滿足這些測試用例。然後迴圈進行新增其他功能,直到完全部功能的開發,在此過程中我們可以藉助一些工具來協助。比如我們現在要研究Nhibernate,那麼我們首先要熟練它的一些功能,然後從一個點出發慢慢編寫單元測試,然後逐漸完善程式碼,最後直至完成框架的搭建,這樣會給我們帶來莫大的驅動力和成就感。除了微軟的BCL(Base Class Library)和企業庫以外,大家還可以用TDD來試試還原以下的任一開原始碼:

四. 依賴屬性續前緣

  大家都知道WPF和Silverlight帶來了很多新的特性,其中一大亮點是引入了一種新的屬性機制——依賴屬性。依賴屬性基本應用在了WPF的所有需要設定屬性的元素。依賴屬性根據多個提供物件來決定它的值(可以是動畫、父類元素、繫結、樣式和模板等),同時這個值也能及時響應變化。所以WPF擁有了依賴屬性後,程式碼寫起來就比較得心應手,功能實現上也變得非常容易了。如果沒有依賴屬性,我們將不得不編寫大量的程式碼。依賴屬性在WPF中用得非常廣泛,具體在以下幾個方面中表現得尤為突出:

UI的強大屬性體系

Property value inheritance(值繼承)

Metadata(強大的元資料)

屬性變化通知,限制、驗證

Resources(資源)

Data binding(資料繫結)

Styles、Template(樣式、模板和風格)

路由事件、附加事件、附加行為乃至命令

Animations、3D(動畫和3D)

WPF Designer Integration(WPF設計、開發整合)

  在上一篇WPF基礎到企業應用系列7——深入剖析依賴屬性中,我們對依賴屬性做了較詳細的介紹,那麼下面我們就簡單回顧一下,其實依賴屬性的實現很簡單,只要做以下步驟就可以實現:

◆ 第一步: 讓所在型別繼承自 DependencyObject基類,在WPF中,我們仔細觀察框架的類圖結構,你會發現幾乎所有的 WPF 控制元件都間接繼承自DependencyObject型別。
◆ 第二步:使用 public static 宣告一個 DependencyProperty的變數,該變數才是真正的依賴屬性 ,看原始碼就知道這裡其實用了簡單的單例模式的原理進行了封裝(建構函式私有),只暴露Register方法給外部呼叫。
◆ 第三步:在靜態建構函式中完成依賴屬性的元資料註冊,並獲取物件引用,看程式碼就知道是把剛才宣告的依賴屬性放入到一個類似於容器的地方,沒有講實現原理之前,請容許我先這麼陳述。
◆ 第四步:在前面的三步中,我們完成了一個依賴屬性的註冊,那麼我們怎樣才能對這個依賴屬性進行讀寫呢?答案就是提供一個依賴屬性的例項化包裝屬性,通過這個屬性來實現具體的讀寫操作。

根據前面的四步操作,我們就可以寫出下面的程式碼:

   1: public class SampleDPClass : DependencyObject
   2:  {
   3:      //宣告一個靜態只讀的DependencyProperty欄位
   4:      public static readonly DependencyProperty SampleProperty;
   5:  
   6:      static SampleDPClass()
   7:      {
   8:          //註冊我們定義的依賴屬性Sample
   9:          SampleProperty = DependencyProperty.Register("Sample", typeof(string), typeof(SampleDPClass),
  10:              new PropertyMetadata("Knights Warrior!", OnValueChanged));
  11:      }
  12:  
  13:      private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  14:      {
  15:          //當值改變時,我們可以在此做一些邏輯處理
  16:      }
  17:  
  18:      //屬性包裝器,通過它來讀取和設定我們剛才註冊的依賴屬性
  19:      public string Sample
  20:      {
  21:          get { return (string)GetValue(SampleProperty); }
  22:          set { SetValue(SampleProperty, value); }
  23:      }
  24:  }

  通過上面的例子可以看出,我們一般.NET屬性是直接對類的一個私有屬性進行封裝,所以讀取值的時候,也就是直接讀取這個欄位;而依賴屬性則是通過呼叫繼承自DependencyObject的GetValue()和SetValue來進行操作,它實際儲存在DependencyProperty的一個IDictionary的鍵-值配對字典中,所以一條記錄中的鍵(Key)就是該屬性的HashCode值,而值(Value)則是我們註冊的DependencyProperty。 回顧了一些基礎知識,那我們下面就開始今天的依賴屬性系統TDD之旅。

五. 引入測試驅動開發

1,引入概念

  由於本篇的依賴屬性體系是基於測試驅動開發完成的,所以我們就先來看一下什麼叫測試驅動開發:測試驅動開發的基本思想就是在開發功能程式碼之前,先編寫測試程式碼。也就是說在明確要開發某個功能後,首先思考如何對這個功能進行測試,並完成測試程式碼的編寫,然後編寫相關的程式碼滿足這些測試用例。然後迴圈進行新增其他功能,直到完全部功能的開發。由於過程很長,在寫的時候也省略了不少步驟,所以有些地方銜接不是那麼的流暢,對此表示非常的抱歉!

2,注意事項

根據自身做專案使用TDD的一點微薄經驗,總結了以下幾個注意事項:

◆ 找準切入點:

  不論是開發一個新的系統還是復原系統,都必須先找準一個或多個切入點,從切入點經歷”測試程式碼-功能程式碼-測試-重構“來逐漸完善整個系統,往往這個切入點就是功能點,就是這個系統具備哪些功能,然後根據這些功能寫出測試用例。

◆ 測試列表:

  大家都知道一個系統或者一個框架都是很龐大的,如果要引入測試驅動開發,首先我們必須要有一個測試列表,在任何階段想新增功能需求問題時,把相關功能點加到測試列表中,然後繼續開發的工作。然後不斷的完成對應的測試用例、功能程式碼、重構。這樣可以避免疏漏的同時也能把控當前的進度。

◆ 測試驅動:

  這個比較核心。完成某個功能,某個類,首先編寫測試程式碼,考慮其如何使用、如何測試。然後在對其進行設計、編碼。這裡也強調先編寫對功能程式碼的判斷用的斷言語句,然後編寫相應的輔助語句。

◆ 良好的程式碼設計及可測性:

功能程式碼設計、開發時應該具有較強的可測試性。應該儘量保持良好的設計原則和程式碼規範,如儘量依賴於介面、儘量高內聚、低耦合等等。

◆ 模組或功能隔離:

  不同程式碼的測試應該相互隔離。對一塊程式碼的測試只考慮此程式碼的測試,不要考慮其實現細節,不然就會陷入一團亂麻之中,這個可以通過MOCK來實現,同時在開始的時候也要劃分好邊界。

◆ 適當引入MOCK:

  在適當情況下引入MOCK來完成單元測試,這種情況尤其是在邊際互動比較多的案例當中,對於互動比較多且複雜的多個類關係可以用MOCK暫時模擬,這是一個不錯的解決方案。

◆ 由小到大、由偏到全、統籌兼顧:

  一個產品或者一個專案是比較大的,所以我們這裡就需要遵循由小到大、由偏到全、統籌兼顧的原則,分解功能和程式碼。把所有的規模大、複雜性高的工作,分解成小的任務來完成,這樣既方便團隊協作,同時也減輕了複雜度,使整個開發一下子變得簡單了許多。

◆ 保持隨時重構的習慣

  很多開發者在經過測試程式碼-功能程式碼-測試通過以後就當完成了任務,其實你會發現隨著其他功能的引入或者使用過程中發現了很多重複、冗餘的程式碼、再或者先前的程式碼結構和設計不太合理,這個時候就需要隨時的進行重構和單元測試,在一方面可以避免產生風險,另一方面可以使系統更加完善。

◆ 隨時進行迴歸:

  在”測試程式碼-功能程式碼-測試-重構“的迴圈中一定要記住多回歸,因為這樣可以保證當前的程式碼是不是會影響到前面的功能,其實只需要看看紅綠燈就行。

◆ 檢視和統計程式碼覆蓋率

  通過前面的步驟之後,我們就要看一下實現的功能是否達到我們的預期目標,除了功能完善之外,還要保證程式碼的覆蓋率,因為它是一個系統穩定與否、可維護性與否的一個重大標誌。

3,工具介入

  以後寫關於TDD的文章可能比較多,同時也都會用到這個工具,所以我們今天對它也稍帶介紹一下,正所謂“工欲善其事,必先利其器”。根據官方文件解釋:TestDriven.NET是Visual Studio的一個TDD外掛,最近版本是TestDriven.NET-3.0.2749 RTM版。其中一些新特性有:支援MSTest、.NET Reflector 6 Pro、VS 2010、Silverlight 4、NUnit 2.5.3,使用專案所用的.NET框架等。 下載地址:http://www.testdriven.net/  

這個工具使用起來比VS自帶的單元測試和測試覆蓋功能好用,所以從2008年開始基本就用它作為一個必備的工具使用。關於它具體的功能和怎麼使用,我們這裡不詳細介紹,網上也有很多文章,大家可以做一下參考和研究。下圖是安裝後以外掛的形式出現在VS中的效果:

2010-9-23 19-42-57

A,基本介紹

  TestDriven.NET原來叫做NUnitAddIn,它是個Visual Studio外掛,集成了如下測試框架:NUnit、MbUnit、 ZaneBug、MSTest、NCover、NCoverExplorer、Reflector、TypeMock、dotTrace和MSBee,它主要面向使用TDD的開發者,主要特性列舉如下:

單鍵執行方法、類、名稱空間、專案和解決方案中的單元測試

能夠快速測試例項方法、靜態方法或屬性

可以直接跳到.NET Reflector中的任何方法、型別、專案或引用中,這個功能提供了相當大的方便

在除錯過程中可以檢視.NET Reflector中的任何模組或堆疊資訊

支援多種單元測試框架,包括NUnit、MbUnit、xUnit和MSTest

測試執行在自己的程序中以消除其他問題和邊際效應

可以輕鬆對任何目標測試進行除錯或執行程式碼覆蓋率測試(比微軟自帶的單元測試和程式碼覆蓋功能要好用多了)

支援所有主流的.NET語言:C#、VB、C++和F#

B,TestDriven.NET 3.0中的新特性:

TestDriven.Net是基於.NET框架的。再由於VS 2010支援使用多個.NET版本,所以支援各個VS版本和工具就沒有問題了

完全支援在VS 2008和VS 2010中使用MSTest

完全支援.NET Reflector 6 Pro

支援NUnit 2.5.3

支援和相容VS 2005、VS 2008、VS 2010幾個版本

支援Silverlight 4的測試

C,相容性

  TestDriven.NET兼容於如下VS版本:Windows XP、Vista、Windows 7、Windows 2000、Windows 2003和Windows 2008(32和64位)上的Visual Studio 2005、2008和2010。官方已經不再對VS 2003支援。

D,版本

企業版:每臺機器一個許可認證

專業版:一般的許可形式

個人版:面向學生、開源開發者和試驗使用者的免費許可(大家可以下載這個版本,個人感覺很好用)

4,關於本篇

  本篇文章沒有明確的寫作意圖,只是最近在深入研究MONO原始碼時有感而發,當然作者本人也只是起到了一個研究者或者剖析者的角色。首先實現最簡單且基本的DependencyProperty.Register功能,然後再實現DependencyObject的GetValue和SetValue,接著實現PropertyMetadata的DefaultValue、PropertyChangedCallback、CoerceValueCallback等功能,然後完善DependencyProperty.Register註冊時新增ValidateValueCallback、RegisterAttached、RegisterAttachedReadOnly、RegisterReadOnly、OverrideMetadata、GetMetadata和AddOwner等相關功能。既然有了這些功能,自然就需要完善PropertyMetadata的IsSealed、Merge和OnApply等相關底層操作。當然在中間還需要DependencyObject的ClearValue、CoerceValue、GetLocalValueEnumerator、ReadLocalValue以及其他的Helper類,這裡就不一一進行說明。對於邊際互動比較多且關聯比較大的操作,採用了Mock進行暫時模擬,在開發完了以後再進行了替換。在開發過程中,隨時進行單元測試和覆蓋率的檢查,這樣可以方便檢視哪些功能還有問題以及整體的進度和質量的監控。

六. DependencyProperty測試程式碼

在寫DependencyProperty測試程式碼之前,我們先看一下它到底有哪些成員和方法,如下圖:

2010-9-26 23-07-52

  瞭解了上面DependencyProperty的基本功能,我們首先建立一個繼承自DependencyObject的類ObjectPoker,由於DependencyObject還沒有被建立,所以我們這裡就先建立它,然後在ObjectPoker類裡面實現我們的經典語句DependencyProperty.Register,由於Register有很多過載,為了方便TDD,就從最簡單的開始(三個引數,不牽涉到元資料類),然後再建立一個ObjectPoker的子類,這是方便後面測試DependencyProperty的相關功能。

   1: class ObjectPoker : DependencyObject
   2: {
   3:     //註冊依賴屬性property1
   4:     public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1", typeof(string), typeof(ObjectPoker));
   5: }
   6:  
   7: class SubclassPoker : ObjectPoker
   8: {
   9: }

  經過上面的測試用例通過以後,自然DependencyProperty.Register的基本功能也就完善了,然後我們來測試一下Register兩個相同的依賴屬性有什麼反應,由於我們為了實現Register時沒有考慮那麼多,所以測試是先會失敗,然後在引入鍵值對的形式來儲存DependencyProperty,然後每個DependencyProperty都用Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode()來區別唯一,所以相同下面的測試用例也將完成。

   1: [Test]
   2: [ExpectedException(typeof(ArgumentException))] 
   3: public void TestMultipleRegisters()
   4: {
   5:     //測試註冊相同名的依賴屬性
   6:     DependencyProperty.Register("p1", typeof(string), typeof(ObjectPoker));
   7:     DependencyProperty.Register("p1", typeof(string), typeof(ObjectPoker));
   8: }

  我們說到依賴屬性系統,其實依賴屬性要依附於DependencyObject才能成為真正的依賴屬性系統。所以我們來測試一下AddOwner,每一個Owner都有自己的元資料,這個時候我們需要完善OverrideMetadata方法,然而OverrideMetadata方法需要用到PropertyMetadata類作為引數,同時需要呼叫PropertyMetadata類的DoMerge方法,我們可以建立該類,然後結合Mock完成該操作。

   1: [Test]
   2: [ExpectedException(typeof(ArgumentException))] 
   3: public void TestMultipleAddOwner()
   4: {
   5:     //測試AddOwner,新增相同型別的Owner
   6:     ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker), new PropertyMetadata());
   7:     ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker), new PropertyMetadata());
   8: }

通過上面的測試用例以後,其實PropertyMetadata的原型已經具備了,然後我們要做的就是測試DependencyProperty的預設元資料和預設元資料的預設值。

   1: [Test]
   2: public void TestDefaultMetadata()
   3: {
   4:     //測試預設元資料
   5:     DependencyProperty p;
   6:     p = DependencyProperty.Register("TestDefaultMetadata1", typeof(string), typeof(ObjectPoker));
   7:     Assert.IsNotNull(p.DefaultMetadata);
   8:  
   9:     //測試元資料的預設值
  10:     p = DependencyProperty.Register("TestDefaultMetadata2", typeof(string), typeof(ObjectPoker), new PropertyMetadata("hi"));
  11:     Assert.IsNotNull(p.DefaultMetadata);
  12:     Assert.AreEqual("hi", p.DefaultMetadata.DefaultValue);
  13: }

  我們都知道一個DependencyProperty可以擁有多個Owner,每個Owner之間的區別就是用PropertyMetadata,那麼這裡就給該DependencyProperty新增一個Owner,然後通過該Owner來獲取元資料。

   1: [Test]
   2: public void TestAddOwnerNullMetadata()
   3: {
   4:     //首先註冊一個依賴屬性,然後再AddOwner,最後根據新的Owner獲取元資料
   5:     DependencyProperty p = DependencyProperty.Register("TestAddOwnerNullMetadata", typeof(string), typeof(ObjectPoker));
   6:     p.AddOwner(typeof(SubclassPoker), null);
   7:  
   8:     PropertyMetadata pm = p.GetMetadata(typeof(SubclassPoker));
   9:     Assert.IsNotNull(pm);
  10: }

  通過上面的測試用例,我們牽涉到了OverrideMetadata方法,當然上面沒有進行實現,這個時候我們可以來實現OverrideMetadata這個方法,首先註冊一個ObjectPoker型別的依賴屬性,然後通過SubclassPoker來OverrideMetadata。

   1: //首先註冊一個依賴屬性,然後再OverrideMetadata
   2: [Test]
   3: [ExpectedException(typeof(ArgumentNullException))]
   4: public void TestOverrideMetadataNullMetadata()
   5: {
   6:     //有Type但PropertyMetadata為null時,OverrideMetadata操作
   7:     DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullMetadata", typeof(string), typeof(ObjectPoker));
   8:     p.OverrideMetadata(typeof(SubclassPoker), null);
   9: }

  上面實現了OverrideMetadata的函式,但是隻是簡單實現,這裡我們可以傳入一個null型別的Type作為測試,當然測試不會通過,然後就修改程式碼直到測試通過吧!

   1: [Test]
   2: [ExpectedException(typeof(ArgumentNullException))]
   3: public void TestOverrideMetadataNullType()
   4: {
   5:     //當Type為null,OverrideMetadata操作
   6:     DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullType", typeof(string), typeof(ObjectPoker));
   7:     p.OverrideMetadata(null, new PropertyMetadata());
   8: }

  如果仔細分析DependencyProperty的原始碼,你會發現有一個DependencyPropertyKey類,這個類到底是幹嘛的呢?其實這個類的主要作用就是建構函式傳入該DependencyProperty,然後通過Type來OverrideMetadata,這裡只是提供了一個簡單的封裝,如果沒有這個類,其他功能照樣正常。

   1: [Test]
   2: [ExpectedException(typeof(InvalidOperationException))]
   3: public void TestReadonlyOverrideMetadata()
   4: {
   5:     //通過DependencyPropertyKey的方式OverrideMetadata
   6:     DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop1",
   7:                                         typeof(double),
   8:                                         typeof(ObjectPoker),
   9:                                         new PropertyMetadata(double.NaN));
  10:     ro_key.DependencyProperty.OverrideMetadata(typeof(SubclassPoker), new PropertyMetadataPoker());
  11: }

最後我們來測試一樣通過DependencyPropertyKey類來註冊一個ReadOnly的依賴屬性,然後進行OverrideMetadata,基本和上一個測試用例類似。

   1: [Test]
   2: public void TestReadonlyOverrideMetadataFromKey()
   3: {
   4:     //通過DependencyPropertyKey的方式OverrideMetadata
   5:     DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop2",
   6:                                         typeof(double),
   7:                                         typeof(ObjectPoker),
   8:                                         new PropertyMetadata(double.NaN));
   9:     ro_key.OverrideMetadata(typeof(SubclassPoker), new PropertyMetadataPoker());
  10: }

  通過上面的測試用例,DependencyProperty類已經基本完成,除了該類,其他諸如DependencyObject、PropertyMetadata、DependencyPropertyKey也已經初步完成,所以我們這裡先以DependencyProperty作為切入點,那麼下面就來看一下剛才建立的DependencyProperty類。

七. DependencyProperty實現程式碼

具體程式碼如下,我們就不做過多闡述,不過有幾點需要注意:

1,一個依賴屬性可能有多個所有者,所以根據每個所有者都有自己的元資料。

2,依賴屬性私有建構函式,作為初始化操作,每個依賴屬性在註冊的時候都會呼叫並初始化資料

3,為了區別不同的依賴屬性,Name、PropertyType、OwnerType的雜湊值取異。

4,註冊依賴屬性有以下幾個種類:Register、RegisterAttached、RegisterAttachedReadOnly和RegisterReadOnly,所以要區別對待。

5,由於一個依賴屬性可能有多個Owner,根據每個Owner都有自己的元資料,所以要有根據Owner的AddOwner、GetMetadata和OverrideMetadata的操作。

   1:  
   2: using System.Collections.Generic;
   3: namespace System.Windows 
   4: {
   5:     public sealed class DependencyProperty
   6:     {
   7:         //一個依賴屬性可能有多個所有者,所以根據每個所有者都有自己的元資料
   8:         private Dictionary<Type,PropertyMetadata> metadataByType = new Dictionary<Type,PropertyMetadata>();
   9:  
  10:         //宣告一個UnsetValue
  11:         public static readonly object UnsetValue = new object ();
  12:  
  13:         //依賴屬性私有建構函式,作為初始化操作,每個依賴屬性在註冊的時候都會呼叫並初始化資料
  14:         private DependencyProperty (bool isAttached, string name, Type propertyType, Type ownerType,
  15:                         PropertyMetadata defaultMetadata,
  16:                         ValidateValueCallback validateValueCallback)
  17:         {
  18:             IsAttached = isAttached;
  19:             DefaultMetadata = (defaultMetadata == null ? new PropertyMetadata() : defaultMetadata);
  20:             Name = name;
  21:             OwnerType = ownerType;
  22:             PropertyType = propertyType;
  23:             ValidateValueCallback = validateValueCallback;
  24:         }
  25:  
  26:         internal bool IsAttached { get; set; }
  27:         public bool ReadOnly { get; private set; }
  28:         public PropertyMetadata DefaultMetadata { get; private set; }
  29:         public string Name { get; private set; }
  30:         public Type OwnerType { get; private set; }
  31:         public Type PropertyType { get; private set; }
  32:         public ValidateValueCallback ValidateValueCallback { get; private set; }
  33:  
  34:         //獲取依賴屬性的編號,暫未實現,在上一篇“WPF基礎到企業應用系列7——深入剖析依賴屬性”有實現,原理是在初始化的時候++
  35:         public int GlobalIndex {            
  36:             get { throw new NotImplementedException (); }
  37:         }
  38:  
  39:         //傳入ownerType增加Owner
  40:         public DependencyProperty AddOwner(Type ownerType)
  41:         {
  42:             return AddOwner (ownerType, null);
  43:         }
  44:  
  45:         //增加所有者,根據ownerType和typeMetadata
  46:         public DependencyProperty AddOwner(Type ownerType, PropertyMetadata typeMetadata)
  47:         {
  48:             if (typeMetadata == null) typeMetadata = new PropertyMetadata ();
  49:             OverrideMetadata (ownerType, typeMetadata);
  50:  
  51:             // MS seems to always return the same DependencyProperty
  52:             return this;
  53:         }
  54:  
  55:         //獲取元資料,依據forType
  56:         public PropertyMetadata GetMetadata(Type forType)
  57:         {
  58:             if (metadataByType.ContainsKey (forType))
  59:                 return metadataByType[forType];
  60:             return null;
  61:         }
  62:  
  63:         //獲取元資料,依據該依賴屬性
  64:         public PropertyMetadata GetMetadata(DependencyObject d)
  65:         {
  66:             if (metadataByType.ContainsKey (d.GetType()))
  67:                 return metadataByType[d.GetType()];
  68:             return null;
  69:         }
  70:  
  71:         //獲取元資料,依據dependencyObjectType
  72:         public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType)
  73:         {
  74:             if (metadataByType.ContainsKey (dependencyObjectType.SystemType))
  75:                 return metadataByType[dependencyObjectType.SystemType];
  76:             return null;
  77:         }
  78:  
  79:         //驗證型別是否有效
  80:         public bool IsValidType(object value)
  81:         {
  82:             return PropertyType.IsInstanceOfType (value);
  83:         }
  84:  
  85:         //驗證值是否有效
  86:         public bool IsValidValue(object value)
  87:         {
  88:             if (!IsValidType (value))
  89:                 return false;
  90:             if (ValidateValueCallback == null)
  91:                 return true;
  92:             return ValidateValueCallback (value);
  93:         }
  94:  
  95:         //重寫元資料,使用PropertyMetadata類的DoMerge方法來操作
  96:         public void OverrideMetadata(Type forType, PropertyMetadata typeMetadata)
  97:         {
  98:             if (forType == null)
  99:                 throw new ArgumentNullException ("forType");
 100:             if (typeMetadata == null)
 101:                 throw new ArgumentNullException ("typeMetadata");
 102: