INotifyPropertyChanged介面,任何WPF、Silverlight甚至是未來WinRT程式員都必須知道的類型。執行上不用多說,建立一個PropertyChangedEventArgs,資料是屬性的名稱。隨著MVVM模式的大範圍應用,讓人們不得重新審視INotifyPropertyChanged的執行方式(因為MVVM中的ViewModel通常是執行INotifyPropertyChanged的),顯然,最簡單的執行方式很不雅觀,如下:
string companyName;
public string CompanyName
{
get { return companyName; }
set
{
if (value != companyName)
{
companyName = value;
//調用PropertyChanged事件
OnPropertyChanged("CompanyName");
}
}
}
於是.NET 3.5後,運算式樹狀架構(Expression Tree)出現帶來了另一種方式,不管你怎樣看待這種方法,但至少開發人員不需要自己定義一個字串代表屬性名稱,此項工作交給了Expression Tree。
這個改進的方法也是比較流行的,比如微軟的Prism架構中的NotificationObject就是這樣做的,Prism中的PropertySupport.ExtractPropertyName<T>方法可以提取運算式樹狀架構中的MemberExpression,並返回PropertyInfo的Name屬性,這樣開發人員只需要定義一個返回指定屬性的Expression Tree(通過Lambda運算式)。
還有一些工程中有ViewModelEx的類型和上述功能也是類似的。
比如:
//繼承Prism中的NotificationObject,後者執行INotifyPropertyChanged
class MyViewModel : NotificationObject
{
private bool _TaskFinished;
public bool TaskFinished
{
get { return _TaskFinished; }
set
{
if (value != _TaskFinished)
{
_TaskFinished = value;
//通過定義Expression Tree,最終提取屬性名稱
RaisePropertyChanged<bool>(() => TaskFinished);
}
}
}
}
終於到了.NET 4.5,微軟對此進行了直接的改進(要知道Expression Tree的直接意圖並不是直接針對此的),.NET 4.5中加入了調用者資訊,具體是如下三個屬性類別型:
CallerMemberName, CallerFilePath, CallerLineNumber。全部在System.Runtime.CompilerServices命名空間內。
分別對應調用方成員名稱,原始碼檔案路徑和原始碼行。
注意不要把調用資訊想象成什麼很神奇的東西,實際上僅僅是編譯中把相應資訊作為常量傳入到參數中,因此調用資訊只能用在具有預設值的方法參數上。
代碼:
//+ using System.Runtime.CompilerServices;
class Program
{
static void Main(string[] args)
{
doo();
}
static void doo([CallerMemberName]string member = null,
[CallerFilePath] string path = null,
[CallerLineNumber] int line = 0)
{
Console.WriteLine("調用我的是:檔案:{0},行數:{1},成員名稱:{2}"
, path, member, line);
}
}
輸出:
調用我的是:檔案:c:\Users\Mgen\Documents\Visual Studio 2012\Projects\Mgen\Mgen\
Program.cs,行數:Main,成員名稱:15
CallerMemberName很智能,當在屬性中調用時,返回的是屬性名稱而不是屬性包裝的背後方法,否則CallerMemberName就沒用了呵呵。
那麼我們可以這樣定義OnPropertyChanged方法,使用CallerMemberName特性:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
接著在屬性的set中直接調用,因為有了CallerMemberName特性,根本不需要任何參數,編譯器會傳入相應的參數,如下:
private int myVar;
public int MyProperty
{
get { return myVar; }
set
{
if (myVar != value)
{
myVar = value;
OnPropertyChanged();
}
}
}
這還沒有完,微軟既然決定好好改進一下,就不會這樣就完了,Visual Studio 2012的Metro應用程式工程中的Common檔案夾中有一個BindableBase類型,必須先繼承INotifyPropertyChanged:
public abstract class BindableBase : INotifyPropertyChanged
他不僅有上面那個OnPropertyChanged方法,還有一個SetProperty方法:
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
就是set中常見的判斷值的邏輯,那麼有了BindableBase,最後的INotifyPropertyChanged執行就是這樣:
class Mgen : BindableBase
{
private int myVar;
public int MyProperty
{
get { return myVar; }
set { SetProperty(ref myVar, value); }
}
}
怎麼樣?碉堡了,我認為。