Обработка PropertyChanged типобезопасным способом - PullRequest
8 голосов
/ 08 сентября 2010

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

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

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

        ....               
    }
}

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

Ответы [ 5 ]

4 голосов
/ 08 сентября 2010

Давайте объявим метод, который может превратить лямбда-выражение в объект Reflection PropertyInfo ( из моего ответа здесь ):

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, поскольку имена свойств больше не являются константами времени компиляции.

3 голосов
/ 23 сентября 2016

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

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

        ....               
    }
}
1 голос
/ 12 мая 2015

Недавнее решение, которое я придумал, заключается в инкапсуляции логики отправки событий в выделенный класс.

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

1 голос
/ 20 декабря 2010

Я избегаю переключения, комбинируя шаблон команды и некоторую логику выражения. Вы включаете case-action в команду. Я проиллюстрирую это, используя структуру Model View Controller. код реального мира - 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();
}
1 голос
/ 08 сентября 2010

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

...