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

4
这些天,我一直在尝试在我的UWP应用程序中实现MVVM模式,作为学习练习,而不使用额外的框架。虽然我仍然很难理解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的this博客文章中,John Shews是这样做的:
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;
        }
}

这是我在之前的一个SO问题中得到@Tomtom建议的内容:

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版本中的Dictionary有什么用?

  • John版本中的SetProperty重载是什么意思?

  • 为什么前两个示例没有像最后一个示例那样有Get方法?不需要吗?

  • 为什么John要添加一个NotificationBase<T>类?这是其他人错过的重要事情吗?

提前感谢。


我无法猜测NotificationBase<T>的目的是什么。它应该被注释掉。看起来字典的用意是让你避免声明私有后备字段,我认为这很愚蠢。我怀疑这两者之所以做出来,是因为有些人只是喜欢为了复杂化而增加不必要的复杂性。使用带有DoSet()的SetProperty重载可以让您传递一个lambda表达式,只有在值发生更改时才会执行该表达式。可能有一些用处。 - 15ee8f99-57ff-4f92-890c-b56153
这些的唯一想法是将对象的设置属性包装在通用SetProperty中,以便您不必繁琐地重复以完全相同的方式引发多个属性的OnPropertyChanges。这看起来完全是可选的,个人认为我不会遵循其中任何一个。 - Wiktor Zychla
1
关于NotificationBase<T>,John在文章中进一步补充道:“使用NotificationBase<T>是一种方便的方式,可以包装现有的业务对象,这些对象尚未支持INPC。” - Marcel Barc
@MarcelBarc 嗯嗯。 - 15ee8f99-57ff-4f92-890c-b56153
1
@Ed 我认为这并不方便,而且肯定会令人困惑。 - Marcel Barc
1个回答

4

Tomtom版本似乎试图使用字典而不是字段;这种方法很灵活 - 像ExpandoObject一样 - 但它可能会出奇地低效,涉及大量额外的对象(字典、键以及字典使用的任何树结构)花费大量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);
    }
}

但是...不要这样做。除了其他的一切之外,这会将所有值类型都装箱。字段是可以的...


我敢打赌,字典噱头只是为了节省备份字段声明的输入量,代价是运行时开销和尴尬的代码。我认识一些人可能会觉得这很聪明。 - 15ee8f99-57ff-4f92-890c-b56153
@EdPlunkett 我看到它就忍不住想尖叫...那么多的方框! - Marc Gravell
2
但是下划线键太远了!又谁知道片段是什么? - 15ee8f99-57ff-4f92-890c-b56153
1
感谢您快速而详细的回答。所以,如果我理解正确,在这种情况下,越简单越好! - Marcel Barc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接