Notify inner changes

mauro.sampietro's Avatar

mauro.sampietro

31 May, 2018 03:04 PM

I'm using PostSharp 4.3.* so i don't know if something similar to the following have already been implemented in later versions of the product.

Suppose i have something like this:

```
[NotifyPropertyChanged]
public class A{ public B PropertyB{get;set;} }

[NotifyPropertyChanged]
public class B{ public C PropertyC{get;set;} }

[NotifyPropertyChanged]
public class C{ public string Text{get;set;} }
```

Suppose i'd like to be notified that an instance of A as changed even if any property of B or even C has changed.
Currently there's no out of the box way to do that (or i'm not aware of that). Something with AggregateAllChanges can help with collections but the same principle does not apply in general to non-collections.

An aspect like the following can solve the problem very easily.

```
[Serializable]
    public sealed class AggregateChanges : LocationInterceptionAspect
    {
        /// <summary>
        /// Method invoked when (instead of) the target field or property is set to a new value.
        /// </summary>
        /// <param name="args">Context information.</param>
        public override void OnSetValue( LocationInterceptionArgs args )
        {
            if( args.Value != null )
            {
                var notifyingObject = args.Value as INotifyPropertyChanged;
                if( notifyingObject != null )
                {
                    notifyingObject.PropertyChanged += ( s, e ) =>
                    {
                        string propertyName = $"{args.LocationName}.{e.PropertyName}";
                        NotifyPropertyChangedServices.SignalPropertyChanged( args.Instance, propertyName );
                    };
                }
            }

            args.ProceedSetValue();
        }
    }
```

The classes would have to be modified like this:

```
[NotifyPropertyChanged]
public class A{ [AggregateChanges] public B PropertyB{get;set;} }

[NotifyPropertyChanged]
public class B{ [AggregateChanges] public C PropertyC{get;set;} }

[NotifyPropertyChanged]
public class C{ public string Text{get;set;} }
```

It's so easy i actually think you did not do it because of there's no convention on how to refer to inner property changes (here i use dots).
I'm asking your thoughts about this.

Thank you.

  1. Support Staff 1 Posted by PostSharp Techn... on 01 Jun, 2018 07:55 AM

    PostSharp Technologies's Avatar

    Hello,

    we actually use a similar mechanism internally in the aspect (different interface/event). As it is INotifyPropertyChanged does not support property chains, just a simple property name. I would expect that this may actually break some components that are consuming notifications from that event.

    In any way, without some limitation this can quickly get out of control as you will be receiving all notifications from the whole object tree, so I would in general advice against that and instead subscribe to INotifyPropertyChanged.PropertyChanged of the child objects (it would a bit work with reflection at runtime).

    Can you describe your use case for getting changes along the hierarchy?

    Best regards,
    Daniel

  2. 2 Posted by mauro.sampietro on 02 Jun, 2018 11:34 AM

    mauro.sampietro's Avatar

    Hello,

    Even though it is possible to incur in a notification flood, I'd like
    to point out that:

    - NotifyPropertyChanged shouldn't automatically introduce child
    notification by default.

    - An aspect similar to the AggregateChanges should be an opt-in.
      Using it you declare that you want the child to notify its changes.
      Not all children could need to notify their changes.
      Notifications from the whole object tree are thus a conscious choice.

    - When firstly dealing with an hierarchy of classes introducing
    NotifyPropertyChanged,
      naively, i expected a child of a type decorated with
    [NotifyPropertyChanged] to raise its changes when its inner properties
    changed.
      I expected any changes to a property named PropertyB of type B in an
    instance of class A to raise 'PropertyB changed' (without specifying a
    detailed property chain) for each change in class B.

    - A flag on AggregateChanges could control whether to propagate the
    name of the inner property that changed (the default being: do not
    propagate inner property name).
      At this point, (since any change to PropertyB in an instance of A
    would be seen from A as 'PropertyB changed' regardless of what
    property actually changed inside the B class) we can even control the
    maximum notification frequency of the child with System.Reactive thus
    avoiding floods.

    Mauro Sampietro

  3. Support Staff 3 Posted by PostSharp Techn... on 04 Jun, 2018 09:14 AM

    PostSharp Technologies's Avatar

    Hello Mauro,

    I understand what do you mean. Actually [AggregateAllChanges] was originally supposed to relay any observed change to the underlying object, but it proved too difficult to implement without other behavioral changes (hence the limitation on collections). It would not be recursive though.

    The problem is that either you have too many notifications, or the subscription and filtering system gets quite complex. I'm not saying that we have achieved some balance :-).

    To me the proper solution would be to specify the behavior on the consumer, not in a host of consumed classes, i.e. [AggregateAllChanges] behaving recursively (enabling this kind of notifications in it's hierarchy). And that is not possible on the current implementation.

    Thanks for your feedback :-).

    Best regards,
    Daniel

Reply to this discussion

Internal reply

Formatting help / Preview (switch to plain text) No formatting (switch to Markdown)

Attaching KB article:

»

Attached Files

You can attach files up to 10MB

If you don't have an account yet, we need to confirm you're human and not a machine trying to post spam.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac