Каковы различия между различными способами реализации INotifyPropertyChanged? - PullRequest
4 голосов
/ 10 мая 2019

В эти дни я пытался реализовать шаблон MVVM в моих приложениях UWP без дополнительной структуры в качестве учебного упражнения. Хотя мне все еще трудно понять реализацию интерфейса INotifyPropertyChanged, поэтому сейчас я много читаю об этом. Я натолкнулся на совершенно разные способы сделать это, но я не понимаю разницу между ними.

Вот что советует csharpcorner здесь :

        public class BaseModel : INotifyPropertyChanged  
    {  
        public event PropertyChangedEventHandler PropertyChanged;  

        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)  
        {  
            if (object.Equals(storage, value)) return false;  
            storage = value;  
            this.OnPropertyChaned(propertyName);  
            return true;  
        }  

        private void OnPropertyChaned(string propertyName)  
        {  
            var eventHandler = this.PropertyChanged;  
            if (eventHandler != null)  
                eventHandler(this, new PropertyChangedEventArgs(propertyName));  
        }  
    }  

Вот как Джон Шьюс делает это в этом сообщении в блоге от msdn:

public class NotificationBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        // SetField (Name, value); // where there is a data member
        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] String property 
           = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            RaisePropertyChanged(property);
            return true;
        }

        // SetField(()=> somewhere.Name = value; somewhere.Name, value) 
        // Advanced case where you rely on another property
        protected bool SetProperty<T>(T currentValue, T newValue, Action DoSet,
            [CallerMemberName] String property = null)
        {
            if (EqualityComparer<T>.Default.Equals(currentValue, newValue)) return false;
            DoSet.Invoke();
            RaisePropertyChanged(property);
            return true;
        }

        protected void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null) 
            { 
              PropertyChanged(this, new PropertyChangedEventArgs(property)); 
            }
        }
    }

    public class NotificationBase<T> : NotificationBase where T : class, new()
    {
        protected T This;

        public static implicit operator T(NotificationBase<T> thing) { return thing.This; }

        public NotificationBase(T thing = null)
        {
            This = (thing == null) ? new T() : thing;
        }
}

И это то, что мне посоветовал @Tomtom для предыдущего вопроса о SO:

public abstract class NotifyBase : INotifyPropertyChanged
{
  private readonly Dictionary<string, object> mapping;

  protected NotifyBase()
  {
    mapping = new Dictionary<string, object>();
  }

  protected void Set<T>(T value, [CallerMemberName] string propertyName = "")
  {
    mapping[propertyName] = value;
    OnPropertyChanged(propertyName);
  }

  protected T Get<T>([CallerMemberName] string propertyName = "")
  {
    if(mapping.ContainsKey(propertyName))
      return (T)mapping[propertyName];
    return default(T);
  }

  public event PropertyChangedEventHandler PropertyChanged;

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

Может кто-нибудь объяснить различия и сказать мне, если одна из этих реализаций лучше? Особенно:

  • Какая польза от Dictionary в версии Tomtom?

  • Что делает перегрузка SetProperty в версии Джона?

  • Почему в первых двух примерах метод Get не похож на последний? Разве это не нужно?

  • Почему Джон добавляет NotificationBase<T> класс? Разве это важно, что другие пропустили?

Заранее спасибо.

1 Ответ

4 голосов
/ 10 мая 2019

Версия Tomtom представляет собой попытку использовать словарь вместо полей; это гибкий - вроде как ExpandoObject - но он может быть удивительно неэффективным, включая множество дополнительных объектов (словарь, ключи, плюс независимо от того, какие древовидные структуры использует словарь) и много циклов ЦП потратили на поиск постоянно .

Если у вас есть много потенциальных полей (и по лотам я имею в виду, как в сотнях), но вы обычно используете только 3 одновременно, тогда это может быть эффективным решением. Аналогично, если структура полностью динамическая (возможно, на основе входных данных, которые вы не контролируете, предположительно в сочетании с ICustomTypeDescriptor), то это может быть полезно. Сочетание словаря и CallerMemberName предполагает, что является , предназначенным для использования со свойствами, что делает это ... очень странным.

Но в целом: я бы использовал более простую версию ref field. Причина, по которой у этого метода нет Get, заключается в том, что вызывающий абонент может уже просто использовать поле напрямую.

Итак: если вы хотите такой код, я бы использовал:

public class MyType : BaseModel
{
    private string _name;
    public string Name {
        get => _name;
        set => SetProperty(ref _name, value);
    }

    private int _id;
    public string Id {
        get => _id;
        set => SetProperty(ref _id, value);
    }
}

Я предполагаю, что версия Tomtom пытается избежать объявления полей, т. Е.

public class MyType : BaseModel
{
    public string Name {
        get => GetProperty<string>();
        set => SetProperty<string>(value);
    }

    public string Id {
        get => Get<int>();
        set => SetProperty<int>(value);
    }
}

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

...