Подтвердить что ты не робот

Имя свойства INotifyPropertyChanged - hardcode vs reflection?

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

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

4b9b3361

Ответ 1

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

Наиболее очевидным примером является привязка данных.

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

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

Вот что я иногда использую в С# 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);

Ответ 2

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

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

Ответ 3

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

Следующий код по-прежнему использует деревья выражений и, следовательно, имеет следующие преимущества: дружественный и обфускационный рефакторинг, но на самом деле он примерно на 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);
        }
    }
}

Ответ 4

В .NET 4.5 (С# 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();
    }
}

Ответ 5

Roman:

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

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));
  }
}

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

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

Приветствия:) Philipp

Ответ 6

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

Ответ 7

Еще один метод ОЧЕНЬ НИЗКИЙ, о котором я могу думать,

Автообновление INotifyPropertyChanged с помощью аспект
AOP: Аспектно-ориентированное программирование

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

Ответ 9

Без неудобства между Hardcode и reflection мой выбор: 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));
        }
    }
}

Для французских ораторов: Améliorez la lisibilité de votre code et simplifiez vous la vie avec notifypropertyweaver

Ответ 10

Кроме того, мы обнаружили проблему, когда получение имени метода работало по-разному в версиях Debug vs. Release:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/244d3f24-4cc4-4925-aebe-85f55b39ec92

(Код, который мы использовали, не был точно отражен в том, как вы предложили, но он убедил нас в том, что hardcoding имя свойства было самым быстрым и надежным решением.)

Ответ 11

Проблема с методом, основанным на отражении, заключается в том, что он довольно дорогой и не очень быстрый. Конечно, он гораздо более гибкий и менее хрупкий для рефакторинга.

Однако, это действительно может повредить производительность, особенно когда вещи часто вызываются. Метод stackframe, также (я считаю), имеет проблемы в CAS (например, ограниченные уровни доверия, такие как XBAP). Лучше всего жестко его кодировать.

Если вы ищете быстрое, гибкое уведомление о свойствах в WPF, есть решение - используйте DependencyObject:) То, для чего он был предназначен. Если вы не хотите принимать зависимость или беспокоиться о проблемах с привязкой к потоку, переместите имя свойства в константу и бум! ваш хороший.

Ответ 12

Возможно, вы захотите полностью исключить INotifyPropertyChanged. Он добавляет ненужный код бухгалтерского учета в ваш проект. Вместо этого используйте Update Controls.NET.

Ответ 14

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

protected void OnPropertyChanged()
{
    OnPropertyChanged(PropertyName);
}

protected string PropertyName
{
    get
    {
        MethodBase mb = new StackFrame(1).GetMethod();
        string name = mb.Name;
        if(mb.Name.IndexOf("get_") > -1)
            name = mb.Name.Replace("get_", "");

        if(mb.Name.IndexOf("set_") > -1)
            name = mb.Name.Replace("set_", "");

        return name;
    }
}

Ответ 16

Так как С# 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;