冒泡 INotifyPropertyChanged 和嵌套属性

发布于 2024-10-20 06:04:08 字数 648 浏览 9 评论 0原文

如果我有以下布局:


public class A : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public B { get; set; }
}

public class B { public C { get; set; } }
public class C { public D { get; set; } }
public class D { public E { get; set; } }

//... add n classes

public class Z
{
    public int Property
    {
        set
        {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Property"));
        }
    }
}

当 ABCDE..Z.Property 更改时,我通知 A 的最简洁方法是什么?

当 A 内部的任何内容发生更改时,我希望将其标记为“脏”,以便我可以告诉系统 A 需要保存。

If I have the following layout:


public class A : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public B { get; set; }
}

public class B { public C { get; set; } }
public class C { public D { get; set; } }
public class D { public E { get; set; } }

//... add n classes

public class Z
{
    public int Property
    {
        set
        {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Property"));
        }
    }
}

What is the cleanest way for me to notify A when A.B.C.D.E...Z.Property changes?

When anything inside of A changes, I want it to be flagged as "dirty" so I can tell the system that A needs to be saved.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

べ繥欢鉨o。 2024-10-27 06:04:08

事实上,我最近正在研究这个完全相同的问题。我的做法是简单地让 B、C、D 等管理自己的 Dirty 状态,然后修改 A 的 IsDirty 属性,如下所示:

public bool IsDirty
{
   get
   {
        return _isDirty || B.IsDirty || C.IsDirty /* etc */;
   }
}

对我来说,这不仅简单,而且使最有意义。如果 A 的任何属性发生了变化,则 A 是脏的,而 B、C、D 等都是 A 的属性。

I was actually working on this exact same problem just recently. My approach was to simply let B, C, D, etc. to manage their own Dirty state, and then modified A's IsDirty property as such:

public bool IsDirty
{
   get
   {
        return _isDirty || B.IsDirty || C.IsDirty /* etc */;
   }
}

To me, this is not only simple, but it makes the most sense. A is dirty if any of it's properties have changed, and B, C, D, etc are all properties of A.

网名女生简单气质 2024-10-27 06:04:08

我没有测试它,但下面的一个应该可以工作。我不记得为什么,但我认为你无法处理 PropertyChanged 事件。您应该声明自己的委托(VoidHandler)。

public delegate void VoidHandler(object sender);

public class B // also C,D,E,...
{
  // A.ItemChanged() will be wired to this SomethingChangedHandler.
  // I heard you are saving. Exclude SomethingChangedHandler from save.
  [field: NonSerialized]
  public VoidHandler SomethingChangedHandler;

  private c;
  public C
  {
    set
    {
      // unwire handler from old instance of C
      if(c != null)
        c.SomethingChangedHandler -= ItemChanged;

      // wire handler to new instance of C
      value.SomethingChangedHandler += ItemChanged;

      c = value;

      // setting c is also change which require notification
      ItemChanged(this);
    }
    get{}
  }

  // notify A about any change in B or in C
  void ItemChanged(object sender)
  {
    if(SomethingChangedHandler != null)
      SomethingChangedHandler(this);
  }
}

I didn't test it, but following one should work. I don't remember why, but I think you cannot handle PropertyChanged events. You should declare your own delegate (VoidHandler).

public delegate void VoidHandler(object sender);

public class B // also C,D,E,...
{
  // A.ItemChanged() will be wired to this SomethingChangedHandler.
  // I heard you are saving. Exclude SomethingChangedHandler from save.
  [field: NonSerialized]
  public VoidHandler SomethingChangedHandler;

  private c;
  public C
  {
    set
    {
      // unwire handler from old instance of C
      if(c != null)
        c.SomethingChangedHandler -= ItemChanged;

      // wire handler to new instance of C
      value.SomethingChangedHandler += ItemChanged;

      c = value;

      // setting c is also change which require notification
      ItemChanged(this);
    }
    get{}
  }

  // notify A about any change in B or in C
  void ItemChanged(object sender)
  {
    if(SomethingChangedHandler != null)
      SomethingChangedHandler(this);
  }
}
宁愿没拥抱 2024-10-27 06:04:08

对于具有公共基类的业务应用程序,我按照

实现 INotifyPropertyChanged 执行 此操作- 是否存在更好的方法?

进行一些修改来检查“冒泡”属性。

基类

 public bool HasAlteredState { get; protected set; }

 public event PropertyChangedEventHandler PropertyChanged;

 private void propertyObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.OnPropertyChanged(e.PropertyName);
        }

 protected virtual void RegisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
        {
            propertyObject.PropertyChanged += new PropertyChangedEventHandler(propertyObject_PropertyChanged);
        }

 protected virtual void DeregisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
        {
            propertyObject.PropertyChanged -= propertyObject_PropertyChanged;
        }

  protected virtual void OnPropertyChanged(string propertyName)
    {
        this.HasAlteredState = true;
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        MemberExpression body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }

    protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
    {
         if (EqualityComparer<T>.Default.Equals(field, value)) return false;

        if (field is INotifyPropertyChanged)
        {
            if (field != null) { this.DeregisterSubPropertyForChangeTracking((INotifyPropertyChanged)field); }
        }
        if (value is INotifyPropertyChanged)
        {
            if (value != null) { this.RegisterSubPropertyForChangeTracking((INotifyPropertyChanged)value); }
        }

        field = value;
        OnPropertyChanged(selectorExpression);
        return true;
    }

子类

private IndividualName _name;
public IndividualName PersonName
        {
            get { return _name; }
            set { SetField(ref _name, value, () => PersonName); }
        }

提供

  1. 简单属性更改通知
  2. 复杂属性更改通知
  3. 来自对象图中更深处的 INotifyPropertyChanged 实现的事件“冒泡”
  4. 编译时检查您的财产“名称”实际上是指您的财产。即避免仅使用字符串时与拼写错误的属性名称相关的令人讨厌的错误。

性能

这种方法会带来相关的性能影响...比仅使用字符串慢大约 20%。也就是说,虽然指标和跟踪显示它速度较慢,但​​我实际上无法分辨出差异,因此这次打击是值得的:我参与开发的各种应用程序的应用程序维护。

替代实现

  1. 如果基类不是一个选项,您可以选择扩展方法路线。
  2. 为了获得更好的性能,您可以使用两种不同的 SetField 方法;第一个 SetNotifyField 将处理本身实现 INotifyPropertyChanged (如上所述)的属性,第二个 SetField 将处理简单属性。即剪掉

    if (field is INotifyPropertyChanged)...

For line of business application that have a common base class I do this as per

Implementing INotifyPropertyChanged - does a better way exist?

with some modifications to check for "bubbling" properties.

Base Class

 public bool HasAlteredState { get; protected set; }

 public event PropertyChangedEventHandler PropertyChanged;

 private void propertyObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.OnPropertyChanged(e.PropertyName);
        }

 protected virtual void RegisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
        {
            propertyObject.PropertyChanged += new PropertyChangedEventHandler(propertyObject_PropertyChanged);
        }

 protected virtual void DeregisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
        {
            propertyObject.PropertyChanged -= propertyObject_PropertyChanged;
        }

  protected virtual void OnPropertyChanged(string propertyName)
    {
        this.HasAlteredState = true;
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        MemberExpression body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }

    protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
    {
         if (EqualityComparer<T>.Default.Equals(field, value)) return false;

        if (field is INotifyPropertyChanged)
        {
            if (field != null) { this.DeregisterSubPropertyForChangeTracking((INotifyPropertyChanged)field); }
        }
        if (value is INotifyPropertyChanged)
        {
            if (value != null) { this.RegisterSubPropertyForChangeTracking((INotifyPropertyChanged)value); }
        }

        field = value;
        OnPropertyChanged(selectorExpression);
        return true;
    }

Sub classes

private IndividualName _name;
public IndividualName PersonName
        {
            get { return _name; }
            set { SetField(ref _name, value, () => PersonName); }
        }

Provides

  1. Simple property change notification
  2. Complex property change notification
  3. Event "bubbling" from INotifyPropertyChanged implementations deeper in the object graph
  4. Compile time checking that your property "name" actually refers to your property. i.e. avoid nasty bugs related to spelling a property name wrong when just using a string.

Performance

There is an associated performance hit to this approach... about 20% slower than just using a string. That said although the metrics and tracing say it's slower I cant actually tell the difference so the hit is worth it re: application maintenance for the kinds of apps I'm involved in developing.

Alternative Implementations

  1. If base class is not an option you could go Extension method route.
  2. For better performance you could have two different SetField methods; the first SetNotifyField would deal with properties which themselves implement INotifyPropertyChanged (as above) and the second SetField would deal with simple properties. i.e. cut out the

    if (field is INotifyPropertyChanged)...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文