Implementing NotifyPropertyChanged without magic strings [duplicate] Implementing NotifyPropertyChanged without magic strings [duplicate] wpf wpf

Implementing NotifyPropertyChanged without magic strings [duplicate]


I did a thorough test of NotifyPropertyChanged to establish the impact of switching to the lambda expressions.

Here were my test results:

enter image description here

As you can see, using the lambda expression is roughly 5 times slower than the plain hard-coded string property change implementation, but users shouldn't fret, because even then it's capable of pumping out a hundred thousand property changes per second on my not so special work computer. As such, the benefit gained from no longer having to hard-code strings and being able to have one-line setters that take care of all your business far outweighs the performance cost to me.

Test 1 used the standard setter implementation, with a check to see that the property had actually changed:

    public UInt64 TestValue1    {        get { return testValue1; }        set        {            if (value != testValue1)            {                testValue1 = value;                InvokePropertyChanged("TestValue1");            }        }    }

Test 2 was very similar, with the addition of a feature allowing the event to track the old value and the new value. Because this features was going to be implicit in my new base setter method, I wanted to see how much of the new overhead was due to that feature:

    public UInt64 TestValue2    {        get { return testValue2; }        set        {            if (value != testValue2)            {                UInt64 temp = testValue2;                testValue2 = value;                InvokePropertyChanged("TestValue2", temp, testValue2);            }        }    }

Test 3 was where the rubber met the road, and I get to show off this new beautiful syntax for performing all observable property actions in one line:

    public UInt64 TestValue3    {        get { return testValue3; }        set { SetNotifyingProperty(() => TestValue3, ref testValue3, value); }    }

Implementation

In my BindingObjectBase class, which all ViewModels end up inheriting, lies the implementation driving the new feature. I've stripped out the error handling so the meat of the function is clear:

protected void SetNotifyingProperty<T>(Expression<Func<T>> expression, ref T field, T value){    if (field == null || !field.Equals(value))    {        T oldValue = field;        field = value;        OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(GetPropertyName(expression), oldValue, value));    }}protected string GetPropertyName<T>(Expression<Func<T>> expression){    MemberExpression memberExpression = (MemberExpression)expression.Body;    return memberExpression.Member.Name;}

All three methods meet at the OnPropertyChanged routine, which is still the standard:

public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e){    PropertyChangedEventHandler handler = PropertyChanged;    if (handler != null)        handler(sender, e);}

Bonus

If anyone's curious, the PropertyChangedExtendedEventArgs is something I just came up with to extend the standard PropertyChangedEventArgs, so an instance of the extension can always be in place of the base. It leverages knowledge of the old value when a property is changed using SetNotifyingProperty, and makes this information available to the handler.

public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs{    public virtual T OldValue { get; private set; }    public virtual T NewValue { get; private set; }    public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)        : base(propertyName)    {        OldValue = oldValue;        NewValue = newValue;    }}


Personally I like to use Microsoft PRISM's NotificationObject for this reason, and I would guess that their code is reasonably optimized since it's created by Microsoft.

It allows me to use code such as RaisePropertyChanged(() => this.Value);, in addition to keeping the "Magic Strings" so you don't break any existing code.

If I look at their code with Reflector, their implementation can be recreated with the code below

public class ViewModelBase : INotifyPropertyChanged{    // Fields    private PropertyChangedEventHandler propertyChanged;    // Events    public event PropertyChangedEventHandler PropertyChanged    {        add        {            PropertyChangedEventHandler handler2;            PropertyChangedEventHandler propertyChanged = this.propertyChanged;            do            {                handler2 = propertyChanged;                PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler)Delegate.Combine(handler2, value);                propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);            }            while (propertyChanged != handler2);        }        remove        {            PropertyChangedEventHandler handler2;            PropertyChangedEventHandler propertyChanged = this.propertyChanged;            do            {                handler2 = propertyChanged;                PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler)Delegate.Remove(handler2, value);                propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);            }            while (propertyChanged != handler2);        }    }    protected void RaisePropertyChanged(params string[] propertyNames)    {        if (propertyNames == null)        {            throw new ArgumentNullException("propertyNames");        }        foreach (string str in propertyNames)        {            this.RaisePropertyChanged(str);        }    }    protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)    {        string propertyName = PropertySupport.ExtractPropertyName<T>(propertyExpression);        this.RaisePropertyChanged(propertyName);    }    protected virtual void RaisePropertyChanged(string propertyName)    {        PropertyChangedEventHandler propertyChanged = this.propertyChanged;        if (propertyChanged != null)        {            propertyChanged(this, new PropertyChangedEventArgs(propertyName));        }    }}public static class PropertySupport{    // Methods    public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)    {        if (propertyExpression == null)        {            throw new ArgumentNullException("propertyExpression");        }        MemberExpression body = propertyExpression.Body as MemberExpression;        if (body == null)        {            throw new ArgumentException("propertyExpression");        }        PropertyInfo member = body.Member as PropertyInfo;        if (member == null)        {            throw new ArgumentException("propertyExpression");        }        if (member.GetGetMethod(true).IsStatic)        {            throw new ArgumentException("propertyExpression");        }        return body.Member.Name;    }}


If you're concerned that the lambda-expression-tree solution might be too slow, then profile it and find out. I suspect the time spent cracking open the expression tree would be quite a bit smaller than the amount of time the UI will spend refreshing in response.

If you find that it is too slow, and you need to use literal strings to meet your performance criteria, then here's one approach I've seen:

Create a base class that implements INotifyPropertyChanged, and give it a RaisePropertyChanged method. That method checks whether the event is null, creates the PropertyChangedEventArgs, and fires the event -- all the usual stuff.

But the method also contains some extra diagnostics -- it does some Reflection to make sure that the class really does have a property with that name. If the property doesn't exist, it throws an exception. If the property does exist, then it memoizes that result (e.g. by adding the property name to a static HashSet<string>), so it doesn't have to do the Reflection check again.

And there you go: your automated tests will start failing as soon as you rename a property but fail to update the magic string. (I'm assuming you have automated tests for your ViewModels, since that's the main reason to use MVVM.)

If you don't want to fail quite as noisily in production, you could put the extra diagnostic code inside #if DEBUG.