如何在不使用string名称的情况下提升PropertyChanged事件

如果能够在不明确指定更改属性的名称的情况下引发“PropertyChanged”事件,那将是一件好事。 我想要做这样的事情:

public string MyString { get { return _myString; } set { ChangePropertyAndNotify<string>(val=>_myString=val, value); } } private void ChangePropertyAndNotify<T>(Action<T> setter, T value) { setter(value); PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(setter.Method.Name)); } } 

在这种情况下,收到的名称是lambda-method的名称:“<set_MyString> b__0”。

  1. 我可以肯定,修剪“<set_”和“> b__0”将始终提供正确的属性名称?
  2. 有没有其他通知有关财产变化(从财产本身)?

谢谢。

添加了C#6答案

在C#6中(以及Visual Studio 2015中随附的任何VB版本),我们都有一个操作符的名称,它使事情变得比以前更容易。 在我下面的原始答案中,我使用了C#5function(调用者信息属性)来处理“自我更改”通知的常见情况。 操作符的名称可以在所有情况下使用,并且在“相关属性更改”通知场景中特别有用。

为了简单起见,我想我会保持调用者的信息属性方法的常见自我更改通知。 input较less意味着错别字和复制/粘贴导致的错误的机会较less…编译器在这里确保您select一个有效的types/成员/variables,但不能确保您select正确的types。 然后使用相关属性更改通知的新nameof运算符很简单。 下面的例子演示了调用者信息属性的关键行为…如果调用者指定了该参数,则该属性对参数没有影响(也就是说,只有当参数被省略时,调用者信息才被提供给参数值来电者)。

同样值得注意的是,Propertyofhanged事件处理程序也可以使用nameof操作符。 现在,您可以使用nameof运算符将事件中的PropertyName值(它是一个string )与特定的属性进行nameof ,从而消除更多的魔法string。

nameof参考信息: https : nameof

例:

 public class Program { void Main() { var dm = new DataModel(); dm.PropertyChanged += propertyChangedHandler; } void propertyChangedHandler(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == nameof(DataModel.NumberSquared)) { //do something spectacular } } } public class DataModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); handler(this, e); } } } public class DataModel : DataModelBase { //a simple property string _something; public string Something { get { return _something; } set { _something = value; OnPropertyChanged(); } } //a property with another related property int _number; public int Number { get { return _number; } set { _number = value; OnPropertyChanged(); OnPropertyChanged(nameof(this.NumberSquared)); } } //a related property public int NumberSquared { get { return Number * Number; } } } 

原来的C#5答案

从C#5开始,最好使用调用者信息属性,这是在编译时解决的,不需要reflection。

我在一个基类中实现了这一点,派生类只是在属性设置器中调用OnPropertyChanged方法。 如果某个属性隐式地改变另一个值,我可以在属性设置器中使用该方法的“显式”版本,这样就不再是“安全的”,而是我只能接受的一种罕见的情况。

另外,你也可以使用这个方法进行自我更改通知,并使用@Jehof给出的相关属性更改通知给出的答案…这将具有没有魔法string的优点,对自我更改通知的常见情况执行速度最快。

这个最新的build议在下面实现(我想我会开始使用它!)

 public class DataModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { OnPropertyChangedExplicit(propertyName); } protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) { var memberExpression = (MemberExpression)projection.Body; OnPropertyChangedExplicit(memberExpression.Member.Name); } void OnPropertyChangedExplicit(string propertyName) { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); handler(this, e); } } } public class DataModel : DataModelBase { //a simple property string _something; public string Something { get { return _something; } set { _something = value; OnPropertyChanged(); } } //a property with another related property int _number; public int Number { get { return _number; } set { _number = value; OnPropertyChanged(); OnPropertyChanged(() => NumberSquared); } } //a related property public int NumberSquared { get { return Number * Number; } } } 

更新 :原始代码不是Windows Phone友好的,因为它依赖于LambdaExpression.Compile()来获取事件源对象。 这里是更新后的扩展方法(也删除了参数检查):

  public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression) { if (handler != null) { var body = propertyExpression.Body as MemberExpression; var expression = body.Expression as ConstantExpression; handler(expression.Value, new PropertyChangedEventArgs(body.Member.Name)); } } 

用法如下。


您可以使用调用属性getter的lambda函数的reflection来获取属性名称。 请注意,您实际上不必调用该lambdaexpression式,只需要它进行reflection即可:

 public static class INotifyPropertyChangedHelper { public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression) { if (handler != null) { var body = propertyExpression.Body as MemberExpression; if (body == null) throw new ArgumentException("'propertyExpression' should be a member expression"); var expression = body.Expression as ConstantExpression; if (expression == null) throw new ArgumentException("'propertyExpression' body should be a constant expression"); object target = Expression.Lambda(expression).Compile().DynamicInvoke(); var e = new PropertyChangedEventArgs(body.Member.Name); handler(target, e); } } public static void Raise<T>(this PropertyChangedEventHandler handler, params Expression<Func<T>>[] propertyExpressions) { foreach (var propertyExpression in propertyExpressions) { handler.Raise<T>(propertyExpression); } } } 

以下是如何在课堂中使用该助手来提高一个或多个属性的事件:

 PropertyChanged.Raise(() => this.Now); PropertyChanged.Raise(() => this.Age, () => this.Weight); 

注意,这个帮助器在PropertyChangednull情况下也是不可操作的。

在下面的例子中,您必须传递3个值(后台字段,新值,属性为lambda),但没有魔术string,只有在真正不相等时才会引发属性更改事件。

 class Sample : INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { this.SetProperty(ref _name, value, () => this.Name); } } protected void SetProperty<T>(ref T backingField, T newValue, Expression<Func<T>> propertyExpression) { if (backingField == null && newValue == null) { return; } if (backingField == null || !backingField.Equals(newValue)) { backingField = newValue; this.OnPropertyChanged(propertyExpression); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyExpression.GetPropertyName())); } } } 

下面的代码包含从lambdaexpression式获取属性名称的扩展方法。

 public static class Extensions { public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> propertyExpression) { return propertyExpression.Body.GetMemberExpression().GetPropertyName(); } public static string GetPropertyName(this MemberExpression memberExpression) { if (memberExpression == null) { return null; } if (memberExpression.Member.MemberType != MemberTypes.Property) { return null; } var child = memberExpression.Member.Name; var parent = GetPropertyName(memberExpression.Expression.GetMemberExpression()); if (parent == null) { return child; } else { return parent + "." + child; } } public static MemberExpression GetMemberExpression(this Expression expression) { var memberExpression = expression as MemberExpression; if (memberExpression != null) { return memberExpression; } var unaryExpression = expression as UnaryExpression; if (unaryExpression != null) { memberExpression = (MemberExpression)unaryExpression.Operand; if (memberExpression != null) { return memberExpression; } } return null; } public static void ShouldEqual<T>(this T actual, T expected, string name) { if (!Object.Equals(actual, expected)) { throw new Exception(String.Format("{0}: Expected <{1}> Actual <{2}>.", name, expected, actual)); } } } 

最后一些testing代码:

 class q3191536 { public static void Test() { var sample = new Sample(); var propertyChanged = 0; sample.PropertyChanged += new PropertyChangedEventHandler((sender, e) => { if (e.PropertyName == "Name") { propertyChanged += 1; } } ); sample.Name = "Budda"; sample.Name.ShouldEqual("Budda", "sample.Name"); propertyChanged.ShouldEqual(1, "propertyChanged"); sample.Name = "Tim"; sample.Name.ShouldEqual("Tim", sample.Name); propertyChanged.ShouldEqual(2, "propertyChanged"); sample.Name = "Tim"; sample.Name.ShouldEqual("Tim", sample.Name); propertyChanged.ShouldEqual(2, "propertyChanged"); } } 

我正在使用扩展方法

 public static class ExpressionExtensions { public static string PropertyName<TProperty>(this Expression<Func<TProperty>> projection) { var memberExpression = (MemberExpression)projection.Body; return memberExpression.Member.Name; } } 

结合以下方法。 该方法在实现INotifyPropertyChanged接口的类中定义(通常是派生其他类的基类)。

 protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) { var e = new PropertyChangedEventArgs(projection.PropertyName()); OnPropertyChanged(e); } 

然后我可以如下提高PropertyChanged-Event

 private double _rate; public double Rate { get { return _rate; } set { if (_rate != value) { _rate = value; OnPropertyChanged(() => Rate ); } } } 

使用这种方法,它很容易重命名属性(在Visual Studio中),使它确保相应的PropertyChanged调用也被更新。

这是我发现的方式:

 public abstract class ViewModel<T> : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public void RaisePropertyChanged(Expression<Func<T, object>> expression) { var propertyName = GetPropertyFromExpression(expression); this.OnPropertyChanged(propertyName); } public string GetPropertyFromExpression(System.Linq.Expressions.Expression expression) { if (expression == null) throw new ArgumentException("Getting property name form expression is not supported for this type."); var lamda = expression as LambdaExpression; if (lamda == null) throw new NotSupportedException("Getting property name form expression is not supported for this type."); var mbe = lamda.Body as MemberExpression; if (mbe != null) return mbe.Member.Name; var unary = lamda.Body as UnaryExpression; if (unary != null) { var member = unary.Operand as MemberExpression; if (member != null) return member.Member.Name; } throw new NotSupportedException("Getting property name form expression is not supported for this type."); } } 

已经发布的解决scheme混合了两个问题:
1)有些需要你创build一个基类并inheritance它。 这是一个巨大的问题,可以在你的类inheritance链中抛出一个扳手,并导致你开始重新devise你的域,只是为了让这样的开发“额外”。
2)虽然现有的解决scheme允许您指定哪个属性通过lambdaexpression式来引发更改的事件,但它们仍然logging和分发属性名称的string表示forms,因为它们依赖于现有的PropertyChangedEventArgs类。 所以实际上使用你的PropertyChanged事件的任何代码仍然需要做一个string比较,它再次打破了你将来可能需要做的自动重构,更不用提你的编译时支持了,而这个窗口是允许的lambdaexpression式而不是string。

这是我的generics版本,它遵循MS启动的相同的事件/委托模式,这意味着没有基类,也不需要扩展方法。

 public class PropertyChangedEventArgs<TObject> : EventArgs { private readonly MemberInfo _property; public PropertyChangedEventArgs(Expression<Func<TObject, object>> expression) { _property = GetPropertyMember(expression); } private MemberInfo GetPropertyMember(LambdaExpression p) { MemberExpression memberExpression; if (p.Body is UnaryExpression) { UnaryExpression ue = (UnaryExpression)p.Body; memberExpression = (MemberExpression)ue.Operand; } else { memberExpression = (MemberExpression)p.Body; } return (PropertyInfo)(memberExpression).Member; } public virtual bool HasChanged(Expression<Func<TObject, object>> expression) { if (GetPropertyMember(expression) == Property) return true; return false; } public virtual MemberInfo Property { get { return _property; } } } public delegate void PropertyChangedEventHandler<TObject>(object sender, PropertyChangedEventArgs<TObject> e); public interface INotifyPropertyChanged<TObject> { event PropertyChangedEventHandler<TObject> PropertyChanged; } 

现在你可以像这样在一个类中使用它:

 public class PagedProduct : INotifyPropertyChanged<PagedProduct> { IPager _pager; public event PropertyChangedEventHandler<PagedProduct> PropertyChanged = delegate { }; public PagedProduct() { } public IPager Pager { get { return _pager; } set { if (value != _pager) { _pager = value; // let everyone know this property has changed. PropertyChanged(this, new PropertyChangedEventArgs<PagedProduct>(a => a.Pager)); } } } } 

最后,您可以侦听该对象上的事件,并确定使用lambdaexpression式更改哪个属性!

 void SomeMethod() { PagedProduct pagedProducts = new PagedProduct(); pagedProducts.PropertyChanged += pagedProducts_PropertyChanged; } void pagedProducts_PropertyChanged(object sender, PropertyChangedEventArgs<PagedProduct> e) { // lambda expression is used to determine if the property we are interested in has changed. no strings here if (e.HasChanged(a => a.Pager)) { // do something mind blowing like ordering pizza with a coupon } } 

我使用一个简单的扩展方法来获取属性名称,以避免魔术string的问题。 它也保持代码的可读性,即明确发生了什么。

扩展方法简单如下:

 public static string GetPropertyName(this MethodBase methodBase) { return methodBase.Name.Substring(4); } 

有了这个,这意味着你的属性集对名称的变化是有弹性的,如下所示:

 private string _name; public string Name { get { return _name; } set { name = value; RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName()); } } 

我在这里写了更多关于这个扩展方法的内容,并且在这里 发布了一个匹配的代码片段 。