Обработка PropertyChanged безопасным для типа способом

было много статей о том, как использовать reflection и LINQ для создания событий PropertyChanged безопасным для типа способом, без использования строк.

но есть ли способ, чтобы потреблять PropertyChanged события в тип-безопасным способом? В настоящее время я делаю это

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

        ....               
    }
}

есть ли способ, чтобы избежать жесткого кодирования строк в операторе switch для обработки различных свойств? Какой - то аналогичный подход на основе LINQ или отражения?

5 ответов


давайте объявим метод, который может превратить лямбда-выражение в рефлексии


С C# 6.0, вы можете использовать nameof. Вы также можете ссылаться на свойство класса без создания экземпляра этого класса.

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

        ....               
    }
}

Джош Смит MVVM Foundation включает класс PropertyObserver, который делает то, что вы хотите.


Я избегаю коммутатора, комбинируя шаблон команды и некоторую логику выражения. Вы инкапсулируете действие case в команду. Я проиллюстрирую это, используя структуру контроллера вида модели. код реального мира-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

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 делегат означает, что он может быть подписан на PropertyChanged событие любого класса, реализующего INotifyPropertyChanged интерфейс.

класс принимает делегатов, таких как часто используемые DelegateCommand используется большинством реализаций WPF, что означает, что его можно использовать без создания подклассы.

класс выглядит так:

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 заботится о проверке PropertyName на PropertyChangedEventArgs и гарантирует, что handler вызывается правыми изменениями свойств.

обратите внимание, что PropertyChangedHandler также принимает предикат, так что делегат обработчика может быть условно отправлен. Класс также позволяет укажите несколько свойств, чтобы один обработчик мог быть привязан к нескольким свойствам за один раз.

это может быть легко расширено с помощью некоторых методов расширений для более удобной регистрации обработчика, который позволяет создавать обработчик событий и подписаться на PropertyChanged событие в одном вызове метода и укажите свойства, используя выражения вместо строк, чтобы достичь чего-то, что выглядит следующим образом:

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

это в основном говорит о том, что когда либо Foo, Bar или Baz изменить свойства handlerMethod будет вызван, если 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 переименовано, программа не сможет скомпилироваться.