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

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

7 ответов

Решение

Редактировать: nameof прибыл в с # 6. Yay!


Здесь нет 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));
    }
}

Код извлекает имя свойства через трассировку стека из метода вызова - это метод установки свойств с именем 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 для получения имени метода set или 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().Name.Substring(4) из установщика свойств. К сожалению, поиск в Google показывает, что он оказывает существенное влияние на производительность. Еще две вещи для рассмотрения:

  • JIT inlining может повлиять на это неожиданным образом. (Stackru.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)
  • Полностью мое мнение здесь, но я думаю, что было бы неплохо иметь операторы fieldof() и methodof() в 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 должны работать для вас.

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

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

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

Другие вопросы по тегам