使用 INotifyPropertyChanged 时指定属性名称的最佳方法是什么?
大多数示例将属性名称硬编码为 PropertyChanged 事件的参数。我正在考虑使用 MethodBase.GetCurrentMethod.Name.Substring(4) 但对反射开销有点不安。
使用 INotifyPropertyChanged 时指定属性名称的最佳方法是什么?
大多数示例将属性名称硬编码为 PropertyChanged 事件的参数。我正在考虑使用 MethodBase.GetCurrentMethod.Name.Substring(4) 但对反射开销有点不安。
不要忘记一件事:PropertyChanged
事件主要由组件使用,这些组件将使用反射来获取命名属性的值。
最明显的例子是数据绑定。
当您触发PropertyChanged
事件时,将属性名称作为参数传递,您应该知道此事件的订阅者可能会通过调用来使用反射,例如GetProperty
(如果它使用 的缓存,则至少是第一次PropertyInfo
),然后GetValue
. 最后一次调用是属性 getter 方法的动态调用 (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;
}
注意使用表达式树来获取属性的名称,以及使用 lambda 表达式作为Expression
:
FirePropertyChanged(p => p.Name);
在 .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();
}
}
这里的反射开销非常大,特别是因为 INotifyPropertyChanged 被调用了很多。如果可以的话,最好只对值进行硬编码。
如果您不关心性能,那么我会查看下面提到的各种方法,并选择需要最少编码量的方法。如果您可以做一些事情来完全消除对显式调用的需要,那么那将是最好的(例如 AOP)。
使用表达式树所涉及的性能损失是由于表达式树的重复解析。
下面的代码仍然使用表达式树,因此具有重构友好和混淆友好的后续优势,但实际上比通常的技术快 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);
}
}
}
罗马:
我想说你甚至不需要“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 构建中坚持使用带有条件验证的字符串参数。Josh Smith 对此发布了一个很好的示例:
实现 INotifyPropertyChanged 的基类
干杯 :) 菲利普
是的,我看到了您建议的功能的使用和简单性,但是当考虑到反射导致的运行成本时,是的,这是一个坏主意,我在这种情况下使用的是正确添加代码片段以利用时间并在触发所有 Notifyproperty 事件时写入属性时出错。
我能想到的另一种非常好的方法是
使用 AOP自动实现 INotifyPropertyChanged
:面向方面的编程
关于 codeproject 的好文章:INotifyPropertyChanged 的 AOP 实现
由于 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;
没有无关紧要,在硬编码和反射之间,我的选择是: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 与 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;
}
}
基于反射的方法的问题在于它相当昂贵,而且速度不是很快。当然,它更加灵活,并且对重构不那么脆弱。
但是,它确实会损害性能,尤其是在频繁调用事物时。stackframe 方法(我相信)在 CAS 中也存在问题(例如受限信任级别,例如 XBAP)。最好硬编码。
如果您在 WPF 中寻找快速、灵活的属性通知,那么有一个解决方案——使用 DependencyObject :) 这就是它的设计目的。如果您不想依赖依赖,或者担心线程关联问题,请将属性名称移动到常量中,然后繁荣!你的好。
您可能希望完全避免 INotifyPropertyChanged。它为您的项目添加了不必要的簿记代码。请考虑改用Update Controls .NET。
看看这篇博文:http: //khason.net/dev/inotifypropertychanged-auto-wiring-or-how-to-get-rid-of-redundant-code