Имя свойства INotifyPropertyChanged - жесткий код против отражения? - PullRequest
41 голосов
/ 26 сентября 2008

Каков наилучший способ указать имя свойства при использовании INotifyPropertyChanged?

Большинство примеров жестко кодируют имя свойства в качестве аргумента в событии PropertyChanged. Я думал об использовании MethodBase.GetCurrentMethod.Name.Substring (4), но мне немного не по себе из-за затрат на отражение.

Ответы [ 16 ]

45 голосов
/ 27 сентября 2008

Не забывайте одну вещь: PropertyChanged событие в основном используется компонентами, которые используют отражение для получения значения именованного свойства.

Самый очевидный пример - привязка данных.

Когда вы запускаете событие PropertyChanged, передавая имя свойства в качестве параметра, вы должны знать, что подписчик этого события может использовать отражение , вызывая, например, GetProperty (по крайней мере, в первый раз, если он использует кэш PropertyInfo), затем GetValue. Этот последний вызов является динамическим вызовом (MethodInfo.Invoke) метода получения свойства, который стоит больше, чем GetProperty, который запрашивает только метаданные. (Обратите внимание, что привязка данных зависит от всего TypeDescriptor , но в реализации по умолчанию используется отражение.)

Так что, конечно, использование имен свойств жесткого кода при запуске PropertyChanged более эффективно, чем использование отражения для динамического получения имени свойства, но ИМХО, важно сбалансировать свои мысли. В некоторых случаях снижение производительности не так критично, и вы могли бы извлечь выгоду из некоего механизма запуска событий со строгой типизацией.

Вот то, что я иногда использую в C # 3.0, когда производительность не будет проблемой:

public class Person : INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return this.name; }
        set 
        { 
            this.name = value;
            FirePropertyChanged(p => p.Name);
        }
    }

    private void FirePropertyChanged<TValue>(Expression<Func<Person, TValue>> propertySelector)
    {
        if (PropertyChanged == null)
            return;

        var memberExpression = propertySelector.Body as MemberExpression;
        if (memberExpression == null)
            return;

        PropertyChanged(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Обратите внимание на использование дерева выражений для получения имени свойства и использование лямбда-выражения в качестве Expression:

FirePropertyChanged(p => p.Name);
20 голосов
/ 03 января 2013

В .NET 4.5 (C # 5.0) есть новый атрибут - CallerMemberName , который помогает избежать жестко закодированных имен свойств, предотвращая появление ошибок, если разработчики решат изменить имя свойства, вот пример:

public event PropertyChangedEventHandler PropertyChanged = delegate { };

public void OnPropertyChanged([CallerMemberName]string propertyName="")
{
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

private string name;
public string Name
{
    get { return name; }
    set 
    { 
        name = value;
        OnPropertyChanged();
    }
}
19 голосов
/ 26 сентября 2008

Затраты на отражение здесь в значительной степени излишни, особенно с учетом того, что INotifyPropertyChanged вызывается много . Лучше просто жестко закодировать значение, если можете.

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

14 голосов
/ 31 июля 2009

Повышение производительности при использовании деревьев выражений связано с повторным разрешением дерева выражений.

Следующий код по-прежнему использует деревья выражений и, следовательно, имеет следующие преимущества, заключающиеся в том, что он удобен для рефакторинга и для запутывания, но на самом деле он примерно на 40% быстрее (очень грубые тесты), чем обычный метод, который состоит в создании объекта PropertyChangedEventArgs уведомление о каждом изменении.

Это быстрее и позволяет избежать снижения производительности дерева выражений, поскольку мы кэшируем статический объект PropertyChangedEventArgs для каждого свойства.

Есть одна вещь, которой я еще не делаю - я намереваюсь добавить некоторый код, который проверяет отладочные сборки, что имя свойства для предоставленного объекта PropertChangedEventArgs совпадает со свойством, в котором он используется - на данный момент с этим кодом разработчик все еще может указать не тот объект.

Проверьте это:

    public class Observable<T> : INotifyPropertyChanged
    where T : Observable<T>
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected static PropertyChangedEventArgs CreateArgs(
        Expression<Func<T, object>> propertyExpression)
    {
        var lambda = propertyExpression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        var propertyInfo = memberExpression.Member as PropertyInfo;

        return new PropertyChangedEventArgs(propertyInfo.Name);
    }

    protected void NotifyChange(PropertyChangedEventArgs args)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, args);
        }
    }
}

public class Person : Observable<Person>
{
    // property change event arg objects
    static PropertyChangedEventArgs _firstNameChangeArgs = CreateArgs(x => x.FirstName);
    static PropertyChangedEventArgs _lastNameChangeArgs = CreateArgs(x => x.LastName);

    string _firstName;
    string _lastName;

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            NotifyChange(_firstNameChangeArgs);
        }
    }

    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            NotifyChange(_lastNameChangeArgs);
        }
    }
}
11 голосов
/ 12 ноября 2008

Roman:

Я бы сказал, что вам даже не понадобится параметр "Person" - соответственно, вполне общий фрагмент кода, подобный приведенному ниже:

private int age;
public int Age
{
  get { return age; }
  set
  {
    age = value;
    OnPropertyChanged(() => Age);
  }
}


private void OnPropertyChanged<T>(Expression<Func<T>> exp)
{
  //the cast will always succeed
  MemberExpression memberExpression = (MemberExpression) exp.Body;
  string propertyName = memberExpression.Member.Name;

  if (PropertyChanged != null)
  {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
}

... однако я предпочитаю придерживаться строковых параметров с условной проверкой в ​​сборках отладки. Джош Смит опубликовал хороший пример этого:

Базовый класс, который реализует INotifyPropertyChanged

ура :) Philipp

6 голосов
/ 26 сентября 2008

Да, я вижу использование и простоту предлагаемой вами функции, но при рассмотрении текущих затрат из-за размышлений, да, это плохая идея. Что я использую для этого сценария, так это наличие Фрагмента кода добавлено правильно, чтобы воспользоваться временем и ошибкой при написании свойства при всех событиях Notifyproperty.

4 голосов
/ 23 апреля 2009

Другой ОЧЕНЬ ХОРОШИЙ метод, о котором я могу подумать, это

Автоматическое внедрение INotifyProperty, измененное с аспектами
АОП: Аспектно-ориентированное программирование

Хорошая статья о проекте кода: AOP Реализация INotifyPropertyChanged

3 голосов
/ 19 апреля 2009

Вы можете быть заинтересованы в этой дискуссии о

«Рекомендации: как реализовать INotifyPropertyChanged right?»

тоже.

2 голосов
/ 08 февраля 2012

Без разницы между жестким кодом и отражением, мой выбор: notifypropertyweaver .

Этот пакет Visual Studio позволяет использовать преимущества рефлексии (удобство обслуживания, читаемость и т. Д.) Без потери производительности.

На самом деле, вам просто нужно реализовать INotifyPropertyChanged, и он добавляет все «уведомления» при компиляции.

Это также полностью параметрируемо, если вы хотите полностью оптимизировать свой код.

Например, с notifypropertyweaver у вас будет этот код в вашем редакторе:

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string GivenNames { get; set; }
    public string FamilyName { get; set; }

    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }
}

Вместо:

public class Person : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    private string givenNames;
    public string GivenNames
    {
        get { return givenNames; }
        set
        {
            if (value != givenNames)
            {
                givenNames = value;
                OnPropertyChanged("GivenNames");
                OnPropertyChanged("FullName");
            }
        }
    }

    private string familyName;
    public string FamilyName
    {
        get { return familyName; }
        set
        {
            if (value != familyName)
            {
                familyName = value;
                OnPropertyChanged("FamilyName");
                OnPropertyChanged("FullName");
            }
        }
    }

    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }

    public virtual void OnPropertyChanged(string propertyName)
    {
        var propertyChanged = PropertyChanged;
        if (propertyChanged != null)
        {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Для говорящих на французском языке: Кодекс защиты и упрощения кодов и упрощения действий по уведомлению о правах собственности

1 голос
/ 04 апреля 2017

Поскольку в C # 6.0 есть ключевое слово nameof () , оно будет оценено во время компиляции, поэтому оно будет иметь производительность в виде жестко заданного значения и защищено от несоответствия с уведомленным свойством.

public event PropertyChangedEventHandler PropertyChanged;

protected void NotifyPropertyChanged(string info)
{       
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
public string SelectedItem
{
    get
    {
        return _selectedItem;
    }
    set
    {
        if (_selectedItem != value)
        {
            _selectedItem = value;
            NotifyPropertyChanged(nameof(SelectedItem));
        }
    }
}
private string _selectedItem;
...