Как сгенерировать событие PropertyChanged, не используя имя строки

Было бы хорошо иметь способность сгенерировать событие '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".

  1. Я могу быть уверен, та обрезка" <набор _" и"> b __, 0 дюймов будут всегда обеспечивать корректное имя свойства?
  2. Там кто-либо другой должен уведомить об измененном свойстве (от свойства сам)?

Спасибо.

41
задан rohancragg 8 July 2010 в 07:52
поделиться

4 ответа

Я использую метод расширения

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 также будет обновлен.

5
ответ дан 27 November 2019 в 00:30
поделиться

В следующем примере вам нужно передать 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");
    }
}
6
ответ дан 27 November 2019 в 00:30
поделиться

Обновление : исходный код не подходит для 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 .

29
ответ дан 27 November 2019 в 00:30
поделиться

Есть несколько подходов к тому, чтобы сделать это без использования имени свойства.

Лучше всего просто почитать блоги.

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

http://justinangel.net/AutomagicallyImplementingINotifyPropertyChanged

1
ответ дан 27 November 2019 в 00:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: