Saturday, July 2, 2016

The base implementation INotifyPropertyChanged

WPF in something repeated the fate js - due to some unresolved issues on the platform level, many are trying to become pioneers on a par with Karl von Dresen.

Problem

In the case of the INPC in the ViewModel properties there are often dependent on others or calculated based on them. For .net 4.0 The situation with the implementation of complicated by the fact that CallerMemberNameAttribute not supported in this version (actually supported if you are a magician and sorcerer).

Decision



foreword
One of the foundations Rikrop.Core.Wpf library is the base class of the object implements the interface INotifyProprtyChanged - ChangeNotifier, which offers its successors the following set of methods:
[DataContract (IsReference = true)]
[Serializable]
public abstract class ChangeNotifier: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void SetProperty <T> (ref T field, T value, [CallerMemberName] string propertyName = "")

    protected void NotifyPropertyChanged ([CallerMemberName] string propertyName = "")
    protected void NotifyPropertyChanged (Expression <Func <object, object >> property)
    protected void NotifyPropertyChanged (Expression <Func <object >> property)
    protected virtual void OnPropertyChanged (string propertyName)

    protected ILinkedPropertyChanged AfterNotify (Expression <Func <object> property)
    protected ILinkedPropertyChanged BeforeNotify (Expression <Func <object >> property)
    protected ILinkedPropertyChanged AfterNotify <T> (T changeNotifier, Expression <Func <T, object >> property)
        where T: INotifyPropertyChanged
    protected ILinkedPropertyChanged BeforeNotify <T> (T changeNotifier, Expression <Func <T, object >> property)
        where T: ChangeNotifier

    protected ILinkedObjectChanged Notify (Expression <Func <object >> property)
}
Here it is worth to specify interfaces and ILinkedPropertyChanged ILinkedObjectChanged:
public interface ILinkedPropertyChanged
{
    ILinkedPropertyChanged Notify (Expression <Func <object >> targetProperty);
    ILinkedPropertyChanged Execute (Action action);
}

public interface ILinkedObjectChanged
{
    ILinkedObjectChanged AfterNotify (Expression <Func <object >> sourceProperty);
    ILinkedObjectChanged AfterNotify <T> (T sourceChangeNotifier, Expression <Func <T, object >> sourceProperty)
            where T: INotifyPropertyChanged;

    ILinkedObjectChanged BeforeNotify (Expression <Func <object >> sourceProperty);
    ILinkedObjectChanged BeforeNotify <T> (T sourceChangeNotifier, Expression <Func <T, object >> sourceProperty)
            where T: ChangeNotifier;
}

Contrived example of using

Where do without an example, which will be called far-fetched and unrealistic? Let's see how to use the different scenarios ChangeNotifier.

We have a device with N sensors of the same type, which displays the mean value from all datchkov. Each sensor displays the measured value and deviation from the mean. If you change the sensor, we must first count the average value, and then notify the change to the sensor. When the average count value, we need to deviations from the average for each sensor.
/// <Summary>
/// Sensor.
/// </ Summary>
public class Sensor: ChangeNotifier
{
    /// <Summary>
    /// Measuring value.
    /// </ Summary>
    public int Value
    {
        get {return _value; }
        set {SetProperty (ref _value, value); }
    }
    private int _value;
    /// <Summary>
    /// Deviations of the measured values ​​from the mean.
    /// </ Summary>
    public double Delta
    {
        get {return _delta; }
        set {SetProperty (ref _delta, value); }
    }
    private double _delta;
    public Sensor (IAvgValueIndicator indicator)
    {
        // For the sake of example, describe the implementation of a little extra
        BeforeNotify (() => Value) .Notify (() => indicator.AvgValue);
        IValueProvider valueProvider = new RandomValueProvider ();
        Value = valueProvider.GetValue (this);
    }
}
/// <Summary>
/// Instrument with sensors, conducting measurements.
/// </ Summary>
public class Device: ChangeNotifier, IAvgValueIndicator
{
    /// <Summary>
    /// The number of sensors.
    /// </ Summary>
    private const int SensorsCount = 3;
    /// <Summary>
    /// A set of sensors in the apparatus.
    /// </ Summary>
    public IReadOnlyCollection <Sensor> Sensors
    {
        get {return _sensors; }
    }
    private IReadOnlyCollection <Sensor> _sensors;
    /// <Summary>
    /// Average value from the sensors.
    /// </ Summary>
    public double AvgValue
    {
        get {return (Sensors.Sum (s => s.Value)) / (double) Sensors.Count; }
    }
    public Device ()
    {
        InitSensors ();
        AfterNotify (() => AvgValue) .Execute (UpdateDelta);
        NotifyPropertyChanged (() => AvgValue);
    }
    private void InitSensors ()
    {
        var sensors = new List <Sensor> ();
        for (int i = 0; i <SensorsCount; i ++)
        {
            var sensor = new Sensor (this);
            // BeforeNotify (sensor, s => s.Value) .Notify (() => AvgValue);
            sensors.Add (sensor);
        }
        _sensors = sensors;
    }
    private void UpdateDelta ()
    {
        foreach (var sensor in Sensors)
            sensor.Delta = Math.Abs ​​(sensor.Value - AvgValue);
    }
}

We are interested a line of code:
SetProperty (ref _delta, value);
NotifyPropertyChanged (() => AvgValue);
AfterNotify (() => AvgValue) .Execute (UpdateDelta);
BeforeNotify (() => Value) .Notify (() => indicator.AvgValue);
BeforeNotify (sensor, s => s.Value) .Notify (() => AvgValue);

Separately analyze each design and look at the implementation of the following methods.

implementation

SetProperty (ref _delta, value)

This code assigns the field passed in the first parameter of the method, the value of the second parameter, and also notifies subscribers of the property changes, the name of which is passed to the third parameter. If the third parameter is not specified, the name of the calling features.
protected void SetProperty <T> (ref T field, T value, [CallerMemberName] string propertyName = "")
{
    if (Equals (field, value))
    {
        return;
    }

    field = value;
    NotifyPropertyChangedInternal (propertyName);
}

NotifyPropertyChanged (() => AvgValue)

All methods of notification about changing objects, whether they accept the expression tree or a string name of the property, eventually produce the following method:
private void NotifyPropertyChanged (PropertyChangedEventHandler handler, string propertyName)
{
    NotifyLinkedPropertyListeners (propertyName, BeforeChangeLinkedChangeNotifierProperties);

    if (handler! = null)
    {
        handler (this, new PropertyChangedEventArgs (propertyName));
    }
    OnPropertyChanged (propertyName);

    NotifyLinkedPropertyListeners (propertyName, AfterChangeLinkedChangeNotifierProperties);
}

private void NotifyLinkedPropertyListeners (string propertyName,
                                            Dictionary <string, LinkedPropertyChangeNotifierListeners> linkedChangeNotifiers)
{
    LinkedPropertyChangeNotifierListeners changeNotifierListeners;
    if (linkedChangeNotifiers.TryGetValue (propertyName, out changeNotifierListeners))
    {
        changeNotifierListeners.NotifyAll ();
    }
}

Every object that inherits ChangeNotifier stores the collection of "property name" ligaments -> "set notification listeners to change the properties":
private Dictionary <string, LinkedPropertyChangeNotifierListeners> AfterChangeLinkedChangeNotifierProperties {get {...}}
private Dictionary <string, LinkedPropertyChangeNotifierListeners> BeforeChangeLinkedChangeNotifierProperties {get {...}}

No comments:

Post a Comment