views:

122

answers:

4

It would be good to have ability to raise 'PropertyChanged' event without explicit specifying the name of changed property. I would like to do something like this:

    public string MyString
    {
        get { return _myString; }
        set
        {
            ChangePropertyAndNotify<string>(val=>_myString=val, value);
        }
    }

    private void ChangePropertyAndNotify<T>(Action<T> setter, T value)
    {
        setter(value);
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(setter.Method.Name));
        }
    }

In this case received name is a name of lambda-method: "<set_MyString>b__0".

  1. Can I be sure, that trimming "<set_" and ">b__0" will always provide the correct property name?
  2. Is there any other to notify about property changed (from property himself)?

Thank you.

+5  A: 

You can get the property name using reflection on a lambda function that calls the property getter. note that you don't actually have to invoke that lambda, you just need it for the reflection:

public static class INotifyPropertyChangedHelper
{
    public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression)
    {
        if (handler != null)
        {
            var body = propertyExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("'propertyExpression' should be a member expression");

            var expression = body.Expression as ConstantExpression;
            if (expression == null)
                throw new ArgumentException("'propertyExpression' body should be a constant expression");

            object target = Expression.Lambda(expression).Compile().DynamicInvoke();

            var e = new PropertyChangedEventArgs(body.Member.Name);
            handler(target, e);
        }
    }

    public static void Raise<T>(this PropertyChangedEventHandler handler, params Expression<Func<T>>[] propertyExpressions)
    {
        foreach (var propertyExpression in propertyExpressions)
        {
            handler.Raise<T>(propertyExpression);
        }
    }
}

Here's how you can use that helper in your class to raise the event for one or multiple properties:

PropertyChanged.Raise(() => this.Now);
PropertyChanged.Raise(() => this.Age, () => this.Weight);

Note that this helper also is a no-op in case the PropertyChanged is null.

Franci Penov
Thank you for answer, it gave some help for me... but what 'target' object is representing here? With your code I get how to access the property name, but general idea is not clear for me. I would be appreciated if you explain. Thanks!
Budda
You create an object that implements INotifyPropertyChanged and particular properties. in my example, I have an object that has properties Now, Age and Weight. Then, when you want to raise a notification for one or more of these properties, you call the Raise extension method on the PropertyChanged event of the object instance with a lambda expression that calls the getter for the property. The extension method uses reflection on the lambda to find the name of the property and then calls the actual handler for the event.
Franci Penov
+1  A: 

In the following example you have to pass 3 values (backing field, new value, property as lambda) but there are no magic strings and property changed event is only raised when it truly isn't equal.

class Sample : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set { this.SetProperty(ref _name, value, () => this.Name); }
    }


    protected void SetProperty<T>(ref T backingField, T newValue, Expression<Func<T>> propertyExpression)
    {
        if (backingField == null && newValue == null)
        {
            return;
        }

        if (backingField == null || !backingField.Equals(newValue))
        {
            backingField = newValue;
            this.OnPropertyChanged(propertyExpression);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyExpression.GetPropertyName()));
        }
    }

}

And the following code contains extension methods to get a property name from a lambda expression.

public static class Extensions
{
    public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> propertyExpression)
    {
        return propertyExpression.Body.GetMemberExpression().GetPropertyName();
    }

    public static string GetPropertyName(this MemberExpression memberExpression)
    {
        if (memberExpression == null)
        {
            return null;
        }

        if (memberExpression.Member.MemberType != MemberTypes.Property)
        {
            return null;
        }

        var child = memberExpression.Member.Name;
        var parent = GetPropertyName(memberExpression.Expression.GetMemberExpression());

        if (parent == null)
        {
            return child;
        }
        else
        {
            return parent + "." + child;
        }
    }

    public static MemberExpression GetMemberExpression(this Expression expression)
    {
        var memberExpression = expression as MemberExpression;

        if (memberExpression != null)
        {
            return memberExpression;
        }

        var unaryExpression = expression as UnaryExpression;


        if (unaryExpression != null)
        {
            memberExpression = (MemberExpression)unaryExpression.Operand;

            if (memberExpression != null)
            {
                return memberExpression;
            }

        }
        return null;
    }

    public static void ShouldEqual<T>(this T actual, T expected, string name)
    {
        if (!Object.Equals(actual, expected))
        {
            throw new Exception(String.Format("{0}: Expected <{1}> Actual <{2}>.", name, expected, actual));
        }
    }

}

Finally some test code:

class q3191536
{
    public static void Test()
    {
        var sample = new Sample();
        var propertyChanged = 0;

        sample.PropertyChanged += 
            new PropertyChangedEventHandler((sender, e) => 
                {
                    if (e.PropertyName == "Name")
                    {
                        propertyChanged += 1;
                    }
                }
            );

        sample.Name = "Budda";

        sample.Name.ShouldEqual("Budda", "sample.Name");
        propertyChanged.ShouldEqual(1, "propertyChanged");

        sample.Name = "Tim";
        sample.Name.ShouldEqual("Tim", sample.Name);
        propertyChanged.ShouldEqual(2, "propertyChanged");

        sample.Name = "Tim";
        sample.Name.ShouldEqual("Tim", sample.Name);
        propertyChanged.ShouldEqual(2, "propertyChanged");
    }
}
Tim Murphy
Fixed the code to compile and added test code.
Tim Murphy
+2  A: 

I´m using this Extension method

public static class ExpressionExtensions {
    public static string PropertyName<TProperty>(this Expression<Func<TProperty>> projection) {
        var memberExpression = (MemberExpression)projection.Body;

        return memberExpression.Member.Name;
    }
}

in combination with the following method. This method is defined in the class that implements INotifyPropertyChanged

    protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) {
        var e = new PropertyChangedEventArgs(projection.PropertyName());

        OnPropertyChanged(e);
    }

With this i can raise the PropertyChanged-Event as follows

private double _rate;
public double Rate {
        get {
            return _rate;
        }
        set {
            if (_rate != value) {
              _rate = value;                     
              OnPropertyChanged(() => Rate );
            }
        }
    }

With this approach, its easy to rename Properties (in Visual Studio), cause it ensures that the corresponding PropertyChanged call is updated too.

Jehof
A: 

There are several approaches to doing this without using a propertyname.

Best to just read the blogs.

http://www.pochet.net/blog/2010/06/25/inotifypropertychanged-implementations-an-overview/

http://justinangel.net/AutomagicallyImplementingINotifyPropertyChanged

Simon