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

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

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

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

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

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

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

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

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

 FirePropertyChanged(p => p.Name); 

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

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

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

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

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

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

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

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

  public class Observable : INotifyPropertyChanged where T : Observable { public event PropertyChangedEventHandler PropertyChanged; protected static PropertyChangedEventArgs CreateArgs( Expression> 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 { // 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); } } } 

Римский:

Я бы сказал, что вам даже не понадобится параметр «Личность» – соответственно, должен использоваться полностью общий fragment, подобный приведенному ниже:

 private int age; public int Age { get { return age; } set { age = value; OnPropertyChanged(() => Age); } } private void OnPropertyChanged(Expression> exp) { //the cast will always succeed MemberExpression memberExpression = (MemberExpression) exp.Body; string propertyName = memberExpression.Member.Name; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } 

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

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

Приветствия: Филипп

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

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

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

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

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

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

слишком.

Без неудобства, между 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

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

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

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

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

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

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

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

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

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

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

Поскольку 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; 
  • Отображение видимости столбцов Bind datagrid MVVM
  • Как захватить щелчок мышью по элементу в ListBox в WPF?
  • WPF TemplateBinding vs RelativeSource TemplatedParent
  • Изменение вида для ViewModel
  • Как получить текущий номер строки?
  • Создание шкалы Viewbox по вертикали, но растяжение по горизонтали
  • WPF: Есть ли способ переопределить часть ControlTemplate без переопределения всего стиля?
  • Настройка цвета фона или WPF (4.0) ListBox - Windows 8
  • Могу ли я наложить окно WPF поверх другого?
  • Как изменить фон для кнопки MouseOver в WPF?
  • Ошибка ReSharper WPF: «Не удается разрешить символ« MyVariable »из-за неизвестного DataContext»
  • Давайте будем гением компьютера.