INotifyPropertyChanged属性名称 – 硬编码与reflection?

使用INotifyPropertyChanged时,指定属性名称的最佳方法是什么?

大多数示例将属性名称硬编码为PropertyChanged事件上的参数。 我正在考虑使用MethodBase.GetCurrentMethod.Name.Substring(4),但是对reflection开销有点不安。

不要忘了一件事: PropertyChanged事件主要被组件使用 ,这些组件将使用reflection来获取指定属性的值。

最明显的例子是数据绑定。

当您触发PropertyChanged事件时,将该属性的名称作为parameter passing,您应该知道此事件的订阅者可能通过调用GetProperty 来使用reflection (至less在第一次使用PropertyInfo的caching时),然后GetValue 。 最后一次调用是属性getter方法的dynamic调用(MethodInfo.Invoke),其成本高于仅查询元数据的GetProperty 。 (请注意,数据绑定依赖于整个TypeDescriptor事物 – 但默认实现使用reflection。)

所以,当射击PropertyChanged比使用reflectiondynamic获取属性的名称,但恕我直言,这是非常重要的平衡你的想法,使用硬代码属性名称。 在某些情况下,性能开销并不那么重要,您可以从强types事件触发机制中受益。

下面是我在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式树来获取属性的名称,以及使用lambdaexpression式作为Expression

 FirePropertyChanged(p => p.Name); 

这里的reflection开销特别大,因为INotifyPropertyChanged被调用了很多 。 如果可以的话,最好只是硬编码。

如果你不关心性能,那么我会看看下面提到的各种方法,并select那些需要最less编码的方法。 如果你可以做一些事情来完全消除显式调用的需要,那么这将是最好的(例如AOP)。

expression式树使用涉及的性能问题是由于expression式树的重复parsing造成的。

下面的代码仍然使用expression式树,因此具有重构友好和模糊友好的优点,但实际上比通常的技术快了大约40%(非常粗糙的testing) – 其中包括为每个更改通知创build一个PropertyChangedEventArgs对象。

它更快,并避免expression式树的性能命中,因为我们caching每个属性的静态PropertyChangedEventArgs对象。

还有一件事我还没有做 – 我打算添加一些代码来检查debugging版本,提供的属性名称为所提供的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); } } } 

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

罗马:

我会说你甚至不需要“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)); } } 

…但是,我更喜欢坚持使用Debug版本中的条件validation的string参数。 约什·史密斯在这方面发布了一个很好的例子

一个实现INotifyPropertyChanged的基类

干杯:) Philipp

是的,我看到你所build议的function的使用和简单性,但是当考虑reflection的运行成本,是的,这是一个坏主意,我用这种情况是有一个代码片段正确添加利用时间并在写入所有Notifyproperty事件触发的属性时出错。

另一个非常好的方法,我能想到的是

使用Aspects自动实现INotifyPropertyChanged
AOP:面向方面的编程

关于codeproject的好文章: AOP实现INotifyPropertyChanged

您可能会参与这个讨论

“最佳实践:如何实施INotifyPropertyChanged权利?

太。

不要被硬伤和反思,我的select是: notifypropertyweaver 。

这个Visual Studio包允许你有reflection的好处(可维护性,可读性,..),而不必失去perfs。

实际上,你只需要实现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éliorezlalisibilitéde votre code et simplifiez vous la vie avec notifypropertyweaver

另外,我们发现在Debug和Release版本中获取方法名称的工作方式不同:

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

(我们使用的代码并不完全如您所build议的那样反映,但它使我们确信,对属性名称进行硬编码是最快和最可靠的解决scheme。)

基于reflection的方法的问题是它相当昂贵,并不是非常快。 当然,它更加灵活,而且更不易于重构。

但是,这真的会伤害到性能,特别是在被频繁调用的情况下。 (我相信)在CAS中也存在问题(例如限制信任级别,如XBAP)。 最好是对它进行硬编码。

如果你在WPF中寻找快速,灵活的属性通知有一个解决scheme – 使用DependencyObject :)这就是它的目的。 如果你不想要依赖,或担心线程亲和力问题,将属性名称移动到一个常量,并繁荣! 你的好。

您可能要完全避免INotifyPropertyChanged。 它为您的项目添加了不必要的簿记代码。 考虑使用更新控件.NET 。

另一种方法: http : //www.codeproject.com/Articles/450688/Enhanced-MVVM-Design-w-Type-Safe-View-Models-TVM

我曾经做过一次这样的实验,从内存中运行正常,并且不需要硬编码string中的所有属性名称。 如果您构build大容量服务器应用程序,性能可能会受到影响,在桌面上您可能永远不会注意到其中的差异。

 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;