MVVM INotifyPropertyChanged с автоматической реализацией имени свойства

По моему мнению, мы можем использовать INofityProperty в приложении стиля MVVM с кодом, подобным следующему

    object _SelectedPerson;
    public object SelectedPerson
    {
        get
        {
            return _SelectedPerson;
        }
        set
        {
            if (_SelectedPerson != value)
            {
                _SelectedPerson = value;
                RaisePropertyChanged("SelectedPerson");
            }
        }
    }

Теперь я видел отличный пример Джоша Смита, где он реализует дополнительный код, чтобы зафиксировать, что произойдет, если разработчик набирает имя свойства, которое не распознается, например, опечатка!

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

    object _SelectedPerson;
    public object SelectedPerson
    {
        get
        {
            return _SelectedPerson;
        }
        set
        {
            if (_SelectedPerson != value)
            {
                _SelectedPerson = value;
                RaisePropertyChanged(Current.Method);
            }
        }
    }

static class Current
{
    public static string Method()
    {
        StackTrace st = new StackTrace();
        return (st.GetFrame(1).GetMethod().Name.Split('_')[1]);            
    }
}

Я могу только предположить, что это всегда будет работать, поскольку событие RaisePropertyChanged всегда встречается в Setter (и если я ошибаюсь, пожалуйста, исправьте меня).

Теперь, пожалуйста, обратите внимание: я не в состоянии действительно попробовать это, потому что на работе (где я могу работать над большими проектами) я все еще на .NET 2.0, и поэтому WPF/MVVM далеко в будущем, но я учусь в свое время.

Итак, мой вопрос от тех, кто его использовал, действительно ли лучше иметь подход, который предупреждает пользователя об ошибке по сравнению с удалением опции для ошибки (или вы чувствуете, что я что-то понял? ); Дело в том, что Джош Смит признан экспертом в этой области, и поэтому, если он предложит этот подход, то, как правило, я буду слепо, но в этом случае я не могу помочь, но викторину и чувствую необходимость понять больше.

Ответ 1

Проблема с использованием StackTrace заключается в том, что он неправильно заполнен в сборках релизов. Чтобы устранить эту проблему, есть несколько подходов к ее устранению и облегчить создание события PropertyChanged для разработчиков.

Посмотрите на этот вопрос: Реализация INotifyPropertyChanged - существует ли лучший способ? и выберите подходящее вам решение:)

Лично я предпочитаю следующее:

// Helper method
public static PropertyChangedEventArgs CreateArguments<TOwner>(Expression<Func<TOwner, object>> Expression) {
    // determine the Name of the property using the Expression
}

// Within the view-model implementations:
private static readonly PropertyChangedEventArgs TitleProperty = CreateArguments<MyViewModel>(m => m.Title);

private string title;

public string Title {
    get { return this.title; }
    set {
        if (!string.Equals(this.title, value) {
            this.title = value;
            this.OnPropertyChanged(TitleProperty);
        }
    }
}

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

Мне также нравится подход .NET 4.5 с использованием CallerMemberNameAttribute, но кажется, что он не работает в портативных библиотеках классов.

Ответ 2

Вы можете выполнить INotifyPropertyChanged через абстрактный базовый класс. Это может выглядеть следующим образом:

        public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Event, fired when the Property has changed
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="propertyExpression">() => this.Param</param>
    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        var propertyName = ExtractPropertyName(propertyExpression);
        OnPropertyChanged(propertyName);
    }

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    /// <summary>
    /// Extracts the propertyname out of the Expression given
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="propertyExpression"></param>
    /// <returns></returns>
    private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        var memberExpression = propertyExpression.Body as MemberExpression;
        return memberExpression == null ? null : memberExpression.Member.Name;
    }
}

В .Net 4.5 вы можете создать класс вроде:

 public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Там вам просто нужно позвонить

OnPropertyChanged();

Ответ 3

Я сделал свою собственную настройку для управления уведомлениями об изменении свойств.

Первая часть - классический класс NotifierBase:

/// <summary>
///   Base class for all notifying objects (model adapters, view models, etc.)
/// </summary>
public abstract class NotifierBase : INotifyPropertyChanged
{
    /// <summary>
    ///   Private reference to UI thread
    /// </summary>
    private readonly System.Windows.Threading.Dispatcher _uiThread;

    /// <summary>
    ///   Default Constructor
    /// </summary>
    protected NotifierBase()
    {
        _uiThread = Application.Current != null ? Application.Current.Dispatcher : System.Windows.Threading.Dispatcher.CurrentDispatcher;
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    /// <summary>
    ///   Explicit raise of a property changed notification
    /// </summary>
    /// <param name="e"> </param>
    protected void Notify(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            //Debug method used to verify that the property we are about to raise is really a member of the current class instance
            CheckProperty(e.PropertyName);

            //raises the notification
            ToUiThread(() => handler(this, e));
        }
    }

    protected void Notify(params PropertyChangedEventArgs[] e)
    {
        foreach (var pcea in e)
        {
            Notify(pcea);
        }
    }

    /// <summary>
    ///   Dispatch an action to the ui thread
    /// </summary>
    /// <param name="action"> Action to dispatch </param>
    protected void ToUiThread(Action action)
    {
        if (_uiThread.CheckAccess()) //if we are already in the UI thread, invoke action
            action();
        else
        {
            //otherwise dispatch in the ui thread
            _uiThread.Invoke(action);
        }
    }

    /// <summary>
    ///   Check if the raised property is a valid property for the current instance type
    /// </summary>
    /// <param name="propertyName"> Name of the raised property </param>
    [DebuggerStepThrough]
    private void CheckProperty(string propertyName)
    {
        Type type = GetType();
        PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        if (properties.Any(pi => pi.Name == propertyName)) return;

        throw new InvalidOperationException(
            string.Format("Trying to raise notification on property \"{0}\" which does not exists on type \"{1}\"",
                          propertyName, type.Name));
    }
}

В принципе, этот класс обеспечивает:  - Легкая функция диспетчеризации потоков пользовательского интерфейса  - проверка отладки на имущество, которое уведомлено  - Функция уведомления нескольких свойств

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

public class Npcea 
{

    public static readonly PropertyChangedEventArgs BarCode = new PropertyChangedEventArgs("BarCode");
    public static readonly PropertyChangedEventArgs Cap = new PropertyChangedEventArgs("Cap");
    public static readonly PropertyChangedEventArgs Code = new PropertyChangedEventArgs("Code");
    public static readonly PropertyChangedEventArgs Status = new PropertyChangedEventArgs("Status");
    public static readonly PropertyChangedEventArgs Comments = new PropertyChangedEventArgs("Comments");
}

В основном, в вашей модели (или адаптере) вашего вида вы просто уведомляете о таких свойствах

    public ResourceStatus Status
    {
        get { return _status; }
        set
        {
            _status = value;
            Notify(Npcea.Status,Npcea.Comments);
        }
    }

Надеюсь, это поможет

- bruno