INotifyPropertyChanged 的​​不同实现方式有什么区别?

What are the differences between the different ways of implementing INotifyPropertyChanged?

这些天我一直在尝试在我的 UWP 应用程序中实现 MVVM 模式,而无需额外的框架作为学习练习。虽然我仍然难以理解 INotifyPropertyChanged 接口的实现,所以我现在正在阅读很多关于它的内容。我遇到了非常不同的方法,但我无法理解它们之间的区别。

这就是 csharpcorner 的建议 here:

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

John Shews 在来自 msdn 的 this 博客 post 中就是这样做的:

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

有人可以解释一下差异并告诉我这些实现中的哪一个更好吗?特别是:

提前致谢。

Tomtom 版本似乎是尝试使用字典而不是字段;这是 灵活的 - 有点像 ExpandoObject - 但它可能出奇地低效,涉及许多额外的对象(字典、键、plus 字典使用的任何树结构) 很多 CPU 周期花费在查找内容上 不断.

如果您有很多潜在字段(我的意思是很多,有数百个),但您通常一次只使用 3 个,那么这可能是一个有效的解决方案.同样,如果结构是 完全动态的 (可能基于您无法控制的输入数据,可能与 ICustomTypeDescriptor 配对),那么它可能会有用。字典和 CallerMemberName 的组合表明这个 打算与属性一起使用,但是,这使得这个......非常奇怪。

但总的来说:我会使用更简单的 ref 现场版本。这没有 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);
    }
}

但是...是的,不要那样做。除了其他所有内容之外,这最终会装箱所有 value-types。字段很好...