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方法可以提取表达式树中的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(() => 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(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); }
}
}
怎么样?碉堡了,我认为。