以类型安全的方式处理PropertyChanged

有很多文章关于如何使用reflection和LINQ以类型安全的方式引发PropertyChanged事件,而不使用字符串。

但有没有办法以类型安全的方式使用 PropertyChanged事件? 目前,我正在这样做

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "Property1": ... case "Property2": ... .... } } 

有没有办法避免在switch语句中硬编码字符串来处理不同的属性? 一些类似LINQ或基于reflection的方法?

让我们声明一个方法,可以将lambda表达式转换为Reflection PropertyInfo对象( 取自我的答案 ):

 public static PropertyInfo GetProperty(Expression> expr) { var member = expr.Body as MemberExpression; if (member == null) throw new InvalidOperationException("Expression is not a member access expression."); var property = member.Member as PropertyInfo; if (property == null) throw new InvalidOperationException("Member in expression is not a property."); return property; } 

然后让我们使用它来获取属性的名称:

 void model_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == GetProperty(() => Property1).Name) { // ... } else if (e.PropertyName == GetProperty(() => Property2).Name) { // ... } } 

遗憾的是,您不能使用switch语句,因为属性名称不再是编译时常量。

使用C#6.0,您可以使用nameof 。 您还可以引用类的属性,而无需创建该类的实例。

 void model_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(ClassName.Property1): ... case nameof(ClassName.Property2): ... .... } } 

Josh Smith的MVVM Foundation包含一个PropertyObserver类, 可以满足您的需求。

我通过组合命令模式和一些表达式逻辑来避免切换。 您将case-action封装在命令中。 我将使用模型视图控制器结构来说明这一点。 现实世界的代码 – WinForms,但它是一样的想法

当在模型中设置Tree属性时,该示例在视图中加载树。

自定义ICommand

 void Execute(); string PropertyName { get; } 

具体命令

  public TreeChangedCommand(TreeModel model, ISelectTreeView selectTreeView,Expression> propertyExpression ) { _model = model; _selectTreeView = selectTreeView; var body = propertyExpression.Body as MemberExpression; _propertyName = body.Member.Name; } 

构造函数控制器

  //handle notify changed event from model _model.PropertyChanged += _model_PropertyChanged; //init commands commands = new List(); commands.Add(new TreeChangedCommand(_model,_mainView,()=>_model.Tree)); 

propertyChanged处理程序

 void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { //find the corresponding command and execute it. (instead of the switch) commands.FirstOrDefault(cmd=>cmd.PropertyName.Equals(e.PropertyName)).Execute(); } 

我提出的最新解决方案是将事件调度逻辑封装到专用类中。

该类有一个名为Handle的公共方法,它与PropertyChangedEventHandler委托具有相同的签名,这意味着它可以订阅实现INotifyPropertyChanged接口的任何类的PropertyChanged事件。

该类接受委托,就像大多数WPF实现使用的常用DelegateCommand一样,这意味着它可以在不必创建子类的情况下使用。

这个类看起来像这样:

 public class PropertyChangedHandler { private readonly Action handler; private readonly Predicate condition; private readonly IEnumerable properties; public PropertyChangedHandler(Action handler, Predicate condition, IEnumerable properties) { this.handler = handler; this.condition = condition; this.properties = properties; } public void Handle(object sender, PropertyChangedEventArgs e) { string property = e.PropertyName ?? string.Empty; if (this.Observes(property) && this.ShouldHandle(property)) { handler(property); } } private bool ShouldHandle(string property) { return condition == null ? true : condition(property); } private bool Observes(string property) { return string.IsNullOrEmpty(property) ? true : !properties.Any() ? true : properties.Contains(property); } } 

然后,您可以注册属性更改事件处理程序,如下所示:

 var eventHandler = new PropertyChangedHandler( handler: p => { /* event handler logic... */ }, condition: p => { /* determine if handler is invoked... */ }, properties: new string[] { "Foo", "Bar" } ); aViewModel.PropertyChanged += eventHandler.Handle; 

PropertyChangedHandler负责检查PropertyNamePropertyChangedEventArgs并确保通过正确的属性更改调用handler

请注意,PropertyChangedHandler还接受谓词,以便可以有条件地调度处理程序委托。 该类还允许您指定多个属性,以便单个处理程序可以一次绑定到多个属性。

这可以使用一些扩展方法轻松扩展,以便更方便地处理程序注册,这允许您在单个方法调用中创建事件处理程序并订阅PropertyChanged事件,并使用表达式而不是字符串指定属性,以实现如下所示的内容:

 aViewModel.OnPropertyChanged( handler: p => handlerMethod(), condition: p => handlerCondition, properties: aViewModel.GetProperties( p => p.Foo, p => p.Bar, p => p.Baz ) ); 

这基本上是说当FooBarBaz属性更改时,如果handlerCondition为true,则将调用handlerCondition

提供了OnPropertychanged方法的重载以涵盖不同的事件注册要求。

例如,如果要注册为任何属性更改事件调用的处理程序并且始终执行,则只需执行以下操作:

 aViewModel.OnPropertyChanged(p => handlerMethod()); 

例如,如果要注册始终执行但仅针对单个特定属性更改的处理程序,则可以执行以下操作:

 aViewModel.OnPropertyChanged( handler: p => handlerMethod(), properties: aViewModel.GetProperties(p => p.Foo) ); 

我发现这种方法在编写WPF MVVM应用程序时非常有用。 想象一下,当有三个属性中的任何一个发生更改时,您希望使命令无效。 使用常规方法,您必须执行以下操作:

 void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "Foo": case "Bar": case "Baz": FooBarBazCommand.Invalidate(); break; .... } } 

如果更改任何viewModel属性的名称,则需要记住更新事件处理程序以选择正确的属性。

使用上面指定的PropertyChangedHandler类,您可以使用以下方法获得相同的结果:

 aViewModel.OnPropertyChanged( handler: p => FooBarBazCommand.Invalidate(), properties: aViewModel.GetProperties( p => p.Foo, p => p.Bar, p => p.Baz ) ); 

这现在具有编译时安全性,因此如果重命名任何viewModel属性,程序将无法编译。