Есть ли хороший строго типизированный способ делать события PropertyChanged в C#?

Это должно быть несколько распространенное событие, чтобы изменить имя свойства и ожидать, что функции переименования в Visual Studio позаботятся обо всех необходимых переименованиях, за исключением имени свойства события PropertyChanged INotifyPropertyChanged. Есть ли лучший способ каким-то образом получить его строго типизированным, поэтому вам не нужно помнить, чтобы вручную переименовать его?

7 ответов


Edit:nameof прибыл в c# 6. Ура!


нет nameof / infoof и т. д.; Это много обсуждается, но это то, что есть.

есть способ сделать это с помощью лямбда-выражений в .NET 3.5 (и разбора дерева выражений), но на самом деле это не стоит накладных расходов. На данный момент я бы просто придерживался строк (и модульных тестов, если вы решили не нарушать его).


using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
class Program : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    static void Main() {
        var p = new Program();
        p.PropertyChanged += (s, a) => Console.WriteLine(a.PropertyName);
        p.Name = "abc";
    }
    protected void OnPropertyChanged<T>(Expression<Func<Program, T>> property) {
        MemberExpression me = property.Body as MemberExpression;
        if (me == null || me.Expression != property.Parameters[0]
              || me.Member.MemberType != MemberTypes.Property) {
            throw new InvalidOperationException(
                "Now tell me about the property");
        }
        var handler = PropertyChanged;
        if (handler != null) handler(this,
          new PropertyChangedEventArgs(me.Member.Name));
    }
    string name;
    public string Name {
        get{return name;}
        set {
            name = value;
            OnPropertyChanged(p=>p.Name);
        }
    }
}

C# 5, похоже, имеет решение. СCallerMemberName атрибут что можно использовать с параметрами (один пример в сети).

class Employee : INotifyPropertyChanged
{
    private string _Name;
    public string Name
    {
        get { return _Name; }

        set
        {
            _Name = value;
            RaisePropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged([CallerMemberName] string caller = "")
    {
        var temp = PropertyChanged;

        if ( temp != null )
        {
            temp( this, new PropertyChangedEventArgs( caller ) );
        }
    }
}

самым простым решением является просмотр трассировки стека и полное удаление каждой явной ссылки на свойство.

public String Name
{
    get { return this.name; }
    set
    {
        if (value != this.name)
        {
            this.RaisePropertyChanging();
            this.name = value;
            this.RaisePropertyChanged();
        }
    }
}
private String name = null;

private void RaisePropertyChanged()
{
    String propertyName =
       new StackTrace().GetFrame(1).GetMethod().Name.SubString(4);

    PropertyChangedEventHandler handler = this.PropertyChanged;
    if (handler != null)
    {
        handler(new PropertyChangedEventArgs(propertyName));
    }
}

код получает имя свойства через трассировку стека из метода caling-это метод setter свойств с именем set_<PropertyName>. Если компилятор больше не следует этому соглашению об именах, код прерывается.

другим решением является получение имени свойства из лямбда-выражения.

public static String GetPropertyNameFromLambdaExpression<TObject, TProperty>(
    Expression<Func<TObject, TProperty>> expression)
{
    return ((MemberExpression)expression.Body).Member.Name;
}

для пример

GetPropertyNameFromLambdaExpression<String, Int32>(s => s.Length)

вернутся "Length " как и предполагалось. Производственная версия кода действительно требует дополнительных проверок и лучшей интеграции в остальную часть кода. Например, можно использовать вывод типа для общих аргументов.

обновление

и есть третье решение-вы можете использовать MethodBase.GetCurrentMethod() внутри свойства getter или setter для получения имени метода setter или getter.

public String Name
{
    get { return this.name; }
    set
    {
        if (value != this.name)
        {
            String propertyName = MethodBase.GetCurentMethod().Name.SubString(4);

            this.RaisePropertyChanging(propertyName);
            this.name = value;
            this.RaisePropertyChanged(propertyName);
        }
    }
}
private String name = null;

теоретически вы можете использовать MethodBase.GetCurrentMethod ().Имя.Подстрока (4) из задатчика свойств. К сожалению, поиск Google показывает, что похоже, что это оказывает значительное влияние на производительность. Еще две вещи:

  • JIT inlining может повлиять на это неожиданными способами. (stackoverflow.com/questions/616779/can-i-check-if-the-c-compiler-inlined-a-method-call)
  • теоретически, вызов IL к MethodBase.GetCurrentMethod () может быть тривиально заменяется JIT во время выполнения инструкцией ldtoken с последующим вызовом MethodBase.GetMethodFromHandle (), который будет очень быстрым. Я думаю, пользователи просто не выразили потребности в этом. (msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldtoken.aspx)
  • полностью мой отзыв, но я думаю, было бы неплохо иметь поле() и метод() операторов в C#. Я считаю, что это значительно повысит надежность инструментов анализа/рефакторинга кода в проекты, которые требуют этой способности.

Не ответ на ваш вопрос, но если вы щелкните правой кнопкой мыши->рефакторинг->переименовать свойство, он также может переименовать соответствующие строки, включая любые строки, соответствующие имени вашего свойства.

Да, это может быть немного опасно.


вы должны проверить эту блоге. Это дает вам возможность сделать это:

string propertyName = TypeHelper.GetPropertyName<User>(u => u.LastProjectCode);

PropertyInfo property1 = TypeHelper.GetProperty((SomeClass o) => o.InstanceProperty.Length);

PropertyInfo property2 = TypeHelper.GetProperty(() => SomeClass.StaticProperty.Length);

переименования в Visual Studio / Resharper / Refactor Pro должны работать для вас.


на PropertyChangedEventArgs принимает только один конструктор, для которого требуется имя свойства в виде строки. Таким образом, по сути, нет-использование INotifyPropertyChanged означает, что на каком-то уровне, будь то высокий или низкий в вашей архитектуре, вам придется работать со строкой и ручным переименованием.