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:
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");
}
}
I´m using the 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. The method is defined in the class that implements the INotifyPropertyChanged interface (Normally a base class from which my other classes are derived).
protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) {
var e = new PropertyChangedEventArgs(projection.PropertyName());
OnPropertyChanged(e);
}
Then 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 );
}
}
}
Using this approach, its easy to rename Properties (in Visual Studio), cause it ensures that the corresponding PropertyChanged call is updated too.