想來這個博客也已經有很久沒更新過了,新年新氣象,現在就開始寫新內容吧。
最初的起因?
在最近的幾個月中我做的開發總是要跟 XAML 打交道,也就是 WPF 啊,Silverlight 啊,WF 啊這些。
在進行 WPF 和 Silverlight 開發的過程中常常要用 MVVM 模式,鑒于網絡上關于 MVVM 的特點的文章一抓一大把,我這里就不進一步解釋 MVVM 了。
可是,在 WPF 和 Silverlight 中應用 MVVM 時,是要在 View Model 的屬性被改變的時候是要觸發 INotifyPropertyChanged.PropertyChanged 事件的,對于 C# 來說這部分是不能用像是“{ get; set; }”這樣簡化的語法來實現的;
而且 PropertyChangedEventArgs 的構造函數接受的是以 System.String 類型為載體的屬性名稱,這也就意味著如果把屬性名稱硬編碼在程序中,一旦日后因為什么原因要對代碼進行重構、改變屬性的名稱,就只能手工修改寫在字符串以匹配更改之后的屬性名稱,在這個過程中也是很容易產生失誤的。
所以往往在這個過程中開發人員可能會感到迷茫——到底 MVVM 是讓事情變簡單了還是變得麻煩了?
解決問題的幾種辦法
知道了實際應用 MVVM 模式時的困難,微軟推出的 Prism 框架中便定義了?NotificationObject 類型,派生自此類型的 View Model 可以通過 RaisePropertyChanged 方法來觸發 PropertyChanged 事件,而 RaisePropertyChanged 方法的其中一個重載形式接受的是簽名為 Func<T> 的 Lambda 表達式,也就是說如果使用這個重載,即使我們在未來修改了屬性的名稱,通過 Visual Studio 的重構工具也會自動把這個改動反映到對 PropertyChanged 事件的觸發中,不需要我們再手工修改字符串。
這個方法很多對表達式樹有了解的人大概都想得到,實際上我自己的 ASP.NET 壓縮模塊和 KyuuBackground 也用了類似的方法。
但是,使用表達式樹的話總會有些額外的開銷的,雖然對于觸發事件之后的一系列連鎖反應來說這個開銷可能并不是什么嚴重問題,但仍然有人覺得代碼不夠整潔,所以后來在隨著 Visual Studio 2012 一起到來的新版本 .NET 框架中支持了一種新的語法,通過在像是 RaisePropertyChanged 這樣的方法的簽名上添加 CallerMemberName 特性,就可以在調用代碼中省去傳遞屬性名稱的這個細節,同時也不用擔心會像使用表達式樹那樣產生額外的開銷。
實用的機能
可惜因為種種原因,我暫時還是只能用 Visual Studio 2010,享受不到 CallerMemberName 的便利性,并且即使是可以省去手工填入屬性名稱的動作,對屬性的后端字段的定義、訪問以及對 RaisePropertyChanged 的調用還是不能省略的,代碼寫著寫著我也煩躁了起來。
就在這個時候,我想起來 Visual Studio 中還有個功能叫做“代碼段”。
有了合適的工具,事情就好辦了,于是我立即動手編寫代碼段。
大體上,代碼段看上去是這樣的:
private $type$ m$name$$end$;public $type$ $name${get { return m$name$; }set{if (value == m$name$) return;m$name$ = value;RaisePropertyChanged(() => $name$);}}
由于后端字段和對外暴露的屬性不可以重名,所以這里加了 m 字母作為前綴用以區分;同時我也經常使用 Prism 或者類似的 Library,所以是向 RaisePropertyChanged 方法中傳遞了一個 Lambda 表達式。
對于不使用 Prism 或者類似 Library 的開發人員,可以把對 RaisePropertyChanged 調用的這一行改為:
RaisePropertyChanged("$name$");
而對于使用 Visual Studio 2012 的開發人員,則可以改為:
RaisePropertyChanged();
附件中包含了完整的 .snippet 文件,可以在解壓之后通過 Visual Studio 的代碼段管理器導入,或者直接放入?My Code Snippets 文件夾中。
MVVM.zip