Имя свойства INotifyPropertyChanged - жесткий код против отражения?

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

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

.NET 4.5 дает хорошее решение этой проблемы с атрибутом [CallerMemberName]. См. Пример его использования в документации MSDN msdn.microsoft.com/en-us/library/…

Kerry Jenkins 06.03.2012 18:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
42
1
20 830
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

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

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

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

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

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

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

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

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

Мне нравится творческий подход к этому. Наверное, не стоит использовать на практике. Но концепция интересная.

TamusJRoyce 12.03.2012 19:38

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

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

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

Ответ принят как подходящий

Не забывайте одну вещь: событие 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);

Это кажется излишним для проблемы изменения собственности. Несмотря на то, что он поддерживает все в рефакторируемом коде, вы получаете намного больше кода, который нужно поддерживать, и этот код менее читабелен, чем исходная концепция OnPropertyChanged («Name») или аналогичного метода.

Orion Adrian 19.03.2010 22:03

+1 за объяснение и подтверждение того, что «подписчик этого события, вероятно, будет использовать отражение, вызвав, например, GetProperty». Мне было интересно, что произошло после того, как объект данных сделал OnPropertyChanged («Имя»).

Lernkurve 17.06.2010 16:29

Очень круто. Как вы думаете, есть ли способ полностью избежать использования строк?

strongriley 30.04.2012 22:35

@strongriley: В представленном здесь решении не используется никакая строка. Так что я не уверен, что понимаю, что вы имеете в виду ...

Romain Verdier 02.05.2012 13:45

Я имею в виду, когда вы потребляете событие. Вместо использования INotifyPropertyChanged, можно ли создать интерфейс, в котором EventArgs не использовал бы строковый литерал на первом месте?

strongriley 02.05.2012 15:48

Обратите внимание, что Microsoft никогда не показывает пример решения для отражения. В .NET 4.5 теперь можно использовать новый атрибут [CallerMemberName] msdn.microsoft.com/en-us/library/…

Bastien Vandamme 27.09.2012 17:12

Есть ли способ, используя Reflection, что вам вообще не пришлось бы вводить часть Name? В приведенном выше примере вам нужно ввести даже больше, чем просто "Name", вам нужно ввести p => p.Name - я думаю, это дает вам строгую типизацию? ... но это, конечно, не избавит вас от каких-либо проблем в том, что касается того, сколько вам нужно печатать.

BrainSlugs83 20.12.2013 11:28

Роман:

Я бы сказал, что вам даже не понадобится параметр "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

Ваше здоровье :) Филипп

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

17 of 26 24.04.2010 18:41

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

Возможно, вас заинтересует обсуждение

«Лучшие практики: как правильно реализовать INotifyPropertyChanged?»

тоже.

Еще один ОЧЕНЬ ПРИЯТНЫЙ метод, о котором я могу думать, - это

Автоматическая реализация INotifyPropertyChanged с аспектами
АОП: аспектно-ориентированное программирование

Хорошая статья о codeproject: Реализация INotifyPropertyChanged в АОП

Я также считаю, что АОП - лучшее решение для такого рода проблем. Но вы знаете, что большинство разработчиков C# на самом деле не знают, что такое АОП.

Bastien Vandamme 27.09.2012 17:15

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

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

Обратите внимание, что на самом деле есть проблема с приведенным выше кодом - он ограничивает T непосредственным подтипом (поскольку непосредственный подтип указывает T как сам). Это означает, что вы не можете использовать CreateArgs при работе со свойствами в классе, который находится ниже по иерархии от Person. Простое исправление этого - сделать метод CreateArgs универсальным, то есть CreateArgs <T> вместо универсального класса Observable. Это немного сложнее использовать, поскольку вам нужно явно указать тип, например: static PropertyChangedEventArgs _firstNameChangeArgs = CreateArgs <Person> (x => x.FirstName);

Phil 25.08.2009 20:32

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

Orion Adrian 19.03.2010 22:02

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

Phil 21.03.2010 07:18

Взгляните на это сообщение в блоге: http://khason.net/dev/inotifypropertychanged-auto-wiring-or-how-to-get-rid-of-redundant-code

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

Этот пакет 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 simpleifiez vous la vie avec notifypropertyweaver

В .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();
    }
}

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

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;

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