以类型安全的方式处理PropertyChanged

时间:2010-09-08 13:30:34

标签: c# silverlight events inotifypropertychanged

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

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

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

        ....               
    }
}

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

5 个答案:

答案 0 :(得分:3)

让我们声明一个可以将lambda表达式转换为Reflection PropertyInfo对象(taken from my answer here)的方法:

public static PropertyInfo GetProperty<T>(Expression<Func<T>> 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语句,因为属性名称不再是编译时常量。

答案 1 :(得分:3)

使用C#6.0,您可以使用nameof。你也可以参考一个班级&#39;没有创建该类实例的属性。

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

        ....               
    }
}

答案 2 :(得分:1)

Josh Smith的MVVM Foundation包含一个PropertyObserver类,可以执行您想要的操作。

答案 3 :(得分:1)

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

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

自定义ICommand

void Execute();
string PropertyName  { get;  }

具体指令

 public TreeChangedCommand(TreeModel model, ISelectTreeView selectTreeView,Expression<Func<object>> 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<ICommand>();
  commands.Add(new TreeChangedCommand(_model,_mainView,()=>_model.Tree));

propertyChanged handler

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();
}

答案 4 :(得分:1)

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

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

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

该课程如下:

DelegateCommand

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

public class PropertyChangedHandler
{
    private readonly Action<string> handler;
    private readonly Predicate<string> condition;
    private readonly IEnumerable<string> properties;

    public PropertyChangedHandler(Action<string> handler, 
        Predicate<string> condition, IEnumerable<string> 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的{​​{1}},并确保通过正确的属性更改调用PropertyName

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

这可以使用一些扩展方法轻松扩展,以便更方便地处理程序注册,这允许您在单个方法调用中创建事件处理程序并订阅PropertyChangedEventArgs事件,并使用表达式而不是字符串来指定属性实现看起来像这样的东西:

handler

这基本上是说如果PropertyChanged为真,则会调用aViewModel.OnPropertyChanged( handler: p => handlerMethod(), condition: p => handlerCondition, properties: aViewModel.GetProperties( p => p.Foo, p => p.Bar, p => p.Baz ) ); FooBar属性更改Baz

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

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

handlerCondition

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

OnPropertychanged

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

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

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

使用上面指定的aViewModel.OnPropertyChanged( handler: p => handlerMethod(), properties: aViewModel.GetProperties(p => p.Foo) ); 类,您可以通过以下方式获得相同的结果:

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

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