自动INotifyPropertyChanged

有没有办法自动得到一个类的属性变化的通知,而不必在每个setter中写入OnPropertyChanged? (我有数百个属性,我想知道他们是否改变了)。


安东build议dynamic代理 。 实际上,我曾经使用过“Castle”库来做类似的事情,虽然它减less了我不得不写的代码数量,但是它为我的程序启动时间(ymmv)增加了大约30秒 – 因为它是运行时解决

我想知道是否有一个编译时间的解决scheme,也许使用编译时间属性…


Slashene和TcKs给出的build议会产生重复的代码 – 不幸的是,并不是所有的属性都是m_Value = value的简单情况 – 它们中的很多在setter中都有自定义代码,所以snippets和xml中的Cookie代码并不是真的可行我的项目也是。

编辑: NotifyPropertyWeaver的作者已弃用工具,赞成更一般的Fody 。 (可以从织布工转换到实体的移动指南 )


Notify Property Weaver Fody是我用于项目的一个非常方便的工具。

它将自身安装为项目中的构build步骤,并在编译期间注入引发PropertyChanged事件的代码。

通过在属性上添加特殊属性来完成PropertyChanged:

 [ImplementPropertyChanged] public string MyProperty { get; set; } 

作为奖励,您还可以指定依赖于其他属性的属性的关系

 [ImplementPropertyChanged] public double Radius { get; set; } [DependsOn("Radius")] public double Area { get { return Radius * Radius * Math.PI; } } 

运算符的名称是在C#6.0中用.NET 4.6和VS2015在2015年7月实现的。以下对于C#仍然有效<6.0

我们使用下面的代码( http://www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx )。 伟大的作品:)

 public static class NotificationExtensions { #region Delegates /// <summary> /// A property changed handler without the property name. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="sender">The object that raised the event.</param> public delegate void PropertyChangedHandler<TSender>(TSender sender); #endregion /// <summary> /// Notifies listeners about a change. /// </summary> /// <param name="EventHandler">The event to raise.</param> /// <param name="Property">The property that changed.</param> public static void Notify(this PropertyChangedEventHandler EventHandler, Expression<Func<object>> Property) { // Check for null if (EventHandler == null) return; // Get property name var lambda = Property 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; } ConstantExpression constantExpression; if (memberExpression.Expression is UnaryExpression) { var unaryExpression = memberExpression.Expression as UnaryExpression; constantExpression = unaryExpression.Operand as ConstantExpression; } else { constantExpression = memberExpression.Expression as ConstantExpression; } var propertyInfo = memberExpression.Member as PropertyInfo; // Invoke event foreach (Delegate del in EventHandler.GetInvocationList()) { del.DynamicInvoke(new[] { constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name) }); } } /// <summary> /// Subscribe to changes in an object implementing INotifiyPropertyChanged. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="ObjectThatNotifies">The object you are interested in.</param> /// <param name="Property">The property you are interested in.</param> /// <param name="Handler">The delegate that will handle the event.</param> public static void SubscribeToChange<T>(this T ObjectThatNotifies, Expression<Func<object>> Property, PropertyChangedHandler<T> Handler) where T : INotifyPropertyChanged { // Add a new PropertyChangedEventHandler ObjectThatNotifies.PropertyChanged += (s, e) => { // Get name of Property var lambda = Property 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; // Notify handler if PropertyName is the one we were interested in if (e.PropertyName.Equals(propertyInfo.Name)) { Handler(ObjectThatNotifies); } }; } } 

例如这样使用:

 public class Employee : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _firstName; public string FirstName { get { return this._firstName; } set { this._firstName = value; this.PropertyChanged.Notify(()=>this.FirstName); } } } private void firstName_PropertyChanged(Employee sender) { Console.WriteLine(sender.FirstName); } employee = new Employee(); employee.SubscribeToChange(() => employee.FirstName, firstName_PropertyChanged); 

该示例中可能存在一些语法错误。 没有testing它。 但是你至less应该有这个概念:)

编辑:我现在看到,你可能已经想要更less的工作,但是…上面的东西,至less使它更容易。 而且你可以防止使用string引用属性的所有可怕问题。

Framework 4.5为我们提供了CallerMemberNameAttribute ,它使得将属性名称作为string传递是不必要的:

 private string m_myProperty; public string MyProperty { get { return m_myProperty; } set { m_myProperty = value; OnPropertyChanged(); } } private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed") { // ... do stuff here ... } 

与Svish的解决scheme类似,只需用无聊的框架function取代lambda awesomeness 😉

如果您正在使用安装了KB2468871的 Framework 4.0, 则可以通过nuget安装Microsoft BCL兼容包 ,该也提供了此属性。

实现一个types安全的INotifyPropertyChanged : 见这里

然后制作自己的代码片段:

 private $Type$ _$PropertyName$; public $Type$ $PropertyName$ { get { return _$PropertyName$; } set { if(value != _$PropertyName$) { _$PropertyName$ = value; OnPropertyChanged(o => o.$PropertyName$); } } } 

使用代码片段devise器 ,你已经完成了! 简单,安全的方式来build立你的INotifyPropertyChanged。

你可以在你的PropertyChanged委托上使用扩展方法,像这样使用它:

 public string Name { get { return name; } set { name = value; PropertyChanged.Raise(() => Name); } } 

订阅特定物业更改:

 var obj = new Employee(); var handler = obj.SubscribeToPropertyChanged( o => o.FirstName, o => Console.WriteLine("FirstName is now '{0}'", o.FirstName)); obj.FirstName = "abc"; // Unsubscribe when required obj.PropertyChanged -= handler; 

扩展方法能够通过检查lambdaexpression式树来确定发件人和属性名称,并且没有主要的性能影响

 public static class PropertyChangedExtensions { public static void Raise<TProperty>( this PropertyChangedEventHandler handler, Expression<Func<TProperty>> property) { if (handler == null) return; var memberExpr = (MemberExpression)property.Body; var propertyName = memberExpr.Member.Name; var sender = ((ConstantExpression)memberExpr.Expression).Value; handler.Invoke(sender, new PropertyChangedEventArgs(propertyName)); } public static PropertyChangedEventHandler SubscribeToPropertyChanged<T, TProperty>( this T obj, Expression<Func<T, TProperty>> property, Action<T> handler) where T : INotifyPropertyChanged { if (handler == null) return null; var memberExpr = (MemberExpression)property.Body; var propertyName = memberExpr.Member.Name; PropertyChangedEventHandler subscription = (sender, eventArgs) => { if (propertyName == eventArgs.PropertyName) handler(obj); }; obj.PropertyChanged += subscription; return subscription; } } 

如果PropertyChanged事件在基types中声明,那么它将不会作为派生类中的委托字段显示。 在这种情况下,解决方法是声明PropertyChangedEventHandlertypes的受保护字段,并显式实现事件的addremove访问器:

 public class Base : INotifyPropertyChanged { protected PropertyChangedEventHandler propertyChanged; public event PropertyChangedEventHandler PropertyChanged { add { propertyChanged += value; } remove { propertyChanged -= value; } } } public class Derived : Base { string name; public string Name { get { return name; } set { name = value; propertyChanged.Raise(() => Name); } } } 

我不知道没有标准的方法,但我知道两个解决方法:

1)编译后PostSharp可以为你做。 这是非常有用的,但每个构build都需要一些时间。

2)自定义工具我Visual Studio。 你可以把它和“部分class级”结合起来。 然后,您可以为您的XML创build自定义工具,并且可以从xml生成源代码。

比如这个xml:

 <type scope="public" type="class" name="MyClass"> <property scope="public" type="string" modifier="virtual" name="Text" notify="true" /> </type> 

可以是这个代码的源代码:

 public partial class MyClass { private string _text; public virtual string Text { get { return this._Text; } set { this.OnPropertyChanging( "Text" ); this._Text = value; this.OnPropertyChanged( "Text" ); } } } 

您可能希望从整体上考虑面向方面的编程

框架=>你可以看看林福

你可以看看Castle或Spring.NET并实现拦截器function?

改善儿童课堂通话活动:

称为谢谢:this.NotifyPropertyChange(()=> PageIndex);

将其添加到NotificationExtensions类中:

  /// <summary> /// <para>Lève l'évènement de changement de valeur sur l'objet <paramref name="sender"/> /// pour la propriété utilisée dans la lambda <paramref name="property"/>.</para> /// </summary> /// <param name="sender">L'objet portant la propriété et l'évènement.</param> /// <param name="property">Une expression lambda utilisant la propriété subissant la modification.</param> public static void NotifyPropertyChange(this INotifyPropertyChanged sender, Expression<Func<Object>> property) { if (sender == null) return; // Récupère le nom de la propriété utilisée dans la lambda en argument LambdaExpression lambda = property as LambdaExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { UnaryExpression unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else { memberExpression = lambda.Body as MemberExpression; } ConstantExpression constantExpression = memberExpression.Expression as ConstantExpression; PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo; // il faut remonter la hierarchie, car meme public, un event n est pas visible dans les enfants FieldInfo eventField; Type baseType = sender.GetType(); do { eventField = baseType.GetField(INotifyPropertyChangedEventFieldName, BindingFlags.Instance | BindingFlags.NonPublic); baseType = baseType.BaseType; } while (eventField == null); // on a trouvé l'event, on peut invoquer tt les delegates liés MulticastDelegate eventDelegate = eventField.GetValue(sender) as MulticastDelegate; if (eventDelegate == null) return; // l'event n'est bindé à aucun delegate foreach (Delegate handler in eventDelegate.GetInvocationList()) { handler.Method.Invoke(handler.Target, new Object[] { sender, new PropertyChangedEventArgs(propertyInfo.Name) }); } } 

我刚刚发现ActiveSharp – 自动INotifyPropertyChanged ,我还没有使用它,但它看起来不错。

引用它的网站…


发送属性更改通知,而不指定属性名称作为string。

相反,写这样的属性:

 public int Foo { get { return _foo; } set { SetValue(ref _foo, value); } // <-- no property name here } 

请注意,不需要将该属性的名称作为string包含在内。 ActiveSharp可靠地,正确地为自己计算出来。 它的工作原理是你的属性实现通过了ref的后台字段(_foo)。 (ActiveSharp使用“通过ref”调用来确定哪个后台字段已通过,并从字段中标识该属性)。

只是为了使实施更快,你可以使用一个片段

http://aaron-hoffman.blogspot.it/2010/09/visual-studio-code-snippet-for-notify.html

在MV-VM模式之后的项目的ViewModel类通常需要从属性的setter中引发“PropertyChanged”事件(以协助INotifyPropertyChanged接口实现)。 这是一个繁琐的任务,希望有一天可以通过使用编译器即服务来解决…

片段核心( 完整的功劳归于作者,谁不是我)是以下内容

  <Code Language= "csharp "> <![CDATA[public $type$ $property$ { get { return _$property$; } set { if (_$property$ != value) { _$property$ = value; OnPropertyChanged($property$PropertyName); } } } private $type$ _$property$; public const string $property$PropertyName = "$property$";$end$]]> </Code> 

Property Changed没有Single实现,可以处理人们想要使用它的每种方式。 最好的办法是生成一个帮手类来为你做这个工作,这里是我使用的一个例子

 /// <summary> /// Helper Class that automates most of the actions required to implement INotifyPropertyChanged /// </summary> public static class HPropertyChanged { private static Dictionary<string, PropertyChangedEventArgs> argslookup = new Dictionary<string, PropertyChangedEventArgs>(); public static string ThisPropertyName([CallerMemberName]string name = "") { return name; } public static string GetPropertyName<T>(Expression<Func<T>> exp) { string rtn = ""; MemberExpression mex = exp.Body as MemberExpression; if(mex!=null) rtn = mex.Member.Name; return rtn; } public static void SetValue<T>(ref T target, T newVal, object sender, PropertyChangedEventHandler handler, params string[] changed) { if (!target.Equals(newVal)) { target = newVal; PropertyChanged(sender, handler, changed); } } public static void SetValue<T>(ref T target, T newVal, Action<PropertyChangedEventArgs> handler, params string[] changed) { if (!target.Equals(newVal)) { target = newVal; foreach (var item in changed) { handler(GetArg(item)); } } } public static void PropertyChanged(object sender,PropertyChangedEventHandler handler,params string[] changed) { if (handler!=null) { foreach (var prop in changed) { handler(sender, GetArg(prop)); } } } public static PropertyChangedEventArgs GetArg(string name) { if (!argslookup.ContainsKey(name)) argslookup.Add(name, new PropertyChangedEventArgs(name)); return argslookup[name]; } } 

编辑:有人build议,我从辅助类转换为价值包装,我一直使用这一个,我觉得它工作得很好

 public class NotifyValue<T> { public static implicit operator T(NotifyValue<T> item) { return item.Value; } public NotifyValue(object parent, T value = default(T), PropertyChangingEventHandler changing = null, PropertyChangedEventHandler changed = null, params object[] dependenies) { _parent = parent; _propertyChanged = changed; _propertyChanging = changing; if (_propertyChanged != null) { _propertyChangedArg = dependenies.OfType<PropertyChangedEventArgs>() .Union( from d in dependenies.OfType<string>() select new PropertyChangedEventArgs(d) ); } if (_propertyChanging != null) { _propertyChangingArg = dependenies.OfType<PropertyChangingEventArgs>() .Union( from d in dependenies.OfType<string>() select new PropertyChangingEventArgs(d) ); } _PostChangeActions = dependenies.OfType<Action>(); } private T _Value; public T Value { get { return _Value; } set { SetValue(value); } } public bool SetValue(T value) { if (!EqualityComparer<T>.Default.Equals(_Value, value)) { OnPropertyChnaging(); _Value = value; OnPropertyChnaged(); foreach (var action in _PostChangeActions) { action(); } return true; } else return false; } private void OnPropertyChnaged() { var handler = _propertyChanged; if (handler != null) { foreach (var arg in _propertyChangedArg) { handler(_parent, arg); } } } private void OnPropertyChnaging() { var handler = _propertyChanging; if(handler!=null) { foreach (var arg in _propertyChangingArg) { handler(_parent, arg); } } } private object _parent; private PropertyChangedEventHandler _propertyChanged; private PropertyChangingEventHandler _propertyChanging; private IEnumerable<PropertyChangedEventArgs> _propertyChangedArg; private IEnumerable<PropertyChangingEventArgs> _propertyChangingArg; private IEnumerable<Action> _PostChangeActions; } 

使用的例子

 private NotifyValue<int> _val; public const string ValueProperty = "Value"; public int Value { get { return _val.Value; } set { _val.Value = value; } } 

然后在构造函数中做

 _val = new NotifyValue<int>(this,0,PropertyChanged,PropertyChanging,ValueProperty ); 

在你的自动属性声明上面使用这个属性

 [NotifyParentProperty(true)] public object YourProperty { get; set; }