Было бы хорошо иметь способность сгенерировать событие 'PropertyChanged' без явного определения названия измененного свойства. Я хотел бы сделать что-то вроде этого:
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));
}
}
В этом случае полученное имя является названием метода лямбды: "<set_MyString> b __ 0".
Спасибо.
Я использую метод расширения
public static class ExpressionExtensions {
public static string PropertyName<TProperty>(this Expression<Func<TProperty>> projection) {
var memberExpression = (MemberExpression)projection.Body;
return memberExpression.Member.Name;
}
}
в сочетании со следующим методом. Метод определен в классе, который реализует интерфейс INotifyPropertyChanged (обычно это базовый класс, от которого производны другие мои классы).
protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) {
var e = new PropertyChangedEventArgs(projection.PropertyName());
OnPropertyChanged(e);
}
Затем я могу поднять PropertyChanged-Event следующим образом
private double _rate;
public double Rate {
get {
return _rate;
}
set {
if (_rate != value) {
_rate = value;
OnPropertyChanged(() => Rate );
}
}
}
Используя этот подход, легко переименовывать свойства (в Visual Studio), потому что это гарантирует, что соответствующий вызов PropertyChanged также будет обновлен.
В следующем примере вам нужно передать 3 значения (поле бэкинга, новое значение, свойство как лямбда), но нет никаких магических строк, и событие изменения свойства поднимается только тогда, когда оно действительно не равно.
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()));
}
}
}
А следующий код содержит методы расширения для получения имени свойства из лямбда-выражения.
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));
}
}
}
Наконец, немного тестового кода:
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");
}
}
Обновление : исходный код не подходит для Windows Phone, поскольку он полагается на LambdaExpression.Compile () для получения объекта источника события. Вот обновленный метод расширения (с удаленными проверками параметров):
public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression)
{
if (handler != null)
{
var body = propertyExpression.Body as MemberExpression;
var expression = body.Expression as ConstantExpression;
handler(expression.Value, new PropertyChangedEventArgs(body.Member.Name));
}
}
Использование остается таким, как показано ниже.
Вы можете получить имя свойства, используя отражение в лямбда-функции, которая вызывает метод получения свойства. обратите внимание, что на самом деле вам не нужно вызывать эту лямбду, она вам просто нужна для отражения:
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);
}
}
}
Вот как вы можете использовать этот помощник в своем классе, чтобы вызвать событие для одного или нескольких свойств:
PropertyChanged.Raise(() => this.Now);
PropertyChanged.Raise(() => this.Age, () => this.Weight);
Обратите внимание, что это помощник также не работает, если PropertyChanged
имеет значение null
.
Есть несколько подходов к тому, чтобы сделать это без использования имени свойства.
Лучше всего просто почитать блоги.
http://www.pochet.net/blog/2010/06/25/inotifypropertychanged-implementations-an-overview/
http://justinangel.net/AutomagicallyImplementingINotifyPropertyChanged