Есть ли хороший строго типизированный способ делать события PropertyChanged в C #? - PullRequest
10 голосов
/ 15 июля 2009

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

Ответы [ 7 ]

8 голосов
/ 15 июля 2009

Редактировать: 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);
        }
    }
}
6 голосов
/ 17 января 2013

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 ) );
        }
    }
}
2 голосов
/ 15 июля 2009

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

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" как ожидается. Производственная версия кода действительно требует дополнительных проверок и лучшей интеграции с остальной частью кода. Например, можно использовать вывод типа для общих аргументов.

UPDATE

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

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;
1 голос
/ 15 июля 2009

Теоретически вы можете использовать MethodBase.GetCurrentMethod (). Name.Substring (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)
  • Полностью мое мнение здесь, но я думаю, что было бы неплохо иметь операторы fieldof () и methodof () в C #. Я считаю, что это значительно повысило бы надежность инструментов анализа / рефакторинга кода в проектах, которым требуется такая возможность.
0 голосов
/ 02 ноября 2009

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

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 должны работать для вас.

0 голосов
/ 15 июля 2009

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

0 голосов
/ 15 июля 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...