Automáticamente INotifyPropertyChanged

¿Hay alguna forma de recibir notificación automática de cambios de propiedad en una clase sin tener que escribir OnPropertyChanged en cada setter? (Tengo cientos de propiedades que quiero saber si han cambiado).


Anton sugiere proxies dynamics . De hecho, he usado la biblioteca “Castle” para algo similar en el pasado, y aunque reduce la cantidad de código que he tenido que escribir, agregó alrededor de 30 segundos al tiempo de inicio de mi progtwig (ymmv), porque es un solución de tiempo de ejecución.

Me pregunto si hay una solución de tiempo de comstackción, tal vez usando atributos de tiempo de comstackción …


Slashene y TcKs dan sugerencias que generan código repetitivo. Desafortunadamente, no todas mis propiedades son un simple caso de m_Value = value. Muchas de ellas tienen código personalizado en los setters, por lo que el código de corte de fragmentos y xml no es realmente factible. mi proyecto tampoco.

EDITAR: El autor de NotifyPropertyWeaver ha dejado de usar la herramienta a favor del Fody más general. (Se encuentra disponible una guía de migración para personas que se mueven de tejedor a fody).


Una herramienta muy conveniente que he usado para mis proyectos es Notify Property Weaver Fody .

Se instala como un paso de comstackción en sus proyectos y durante la comstackción inyecta código que genera el evento PropertyChanged .

Hacer que las propiedades suban PropertyChanged se hace poniendo atributos especiales sobre ellas:

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

Como beneficio adicional, también puede especificar relaciones para las propiedades que dependen de otras propiedades

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

El operador nameof se implementó en C # 6.0 con .NET 4.6 y VS2015 en julio de 2015. Lo siguiente sigue siendo válido para C # <6.0

Usamos el código a continuación (De http://www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx ). Funciona genial 🙂

 public static class NotificationExtensions { #region Delegates ///  /// A property changed handler without the property name. ///  ///  /// The object that raised the event. public delegate void PropertyChangedHandler(TSender sender); #endregion ///  /// Notifies listeners about a change. ///  /// The event to raise. /// The property that changed. public static void Notify(this PropertyChangedEventHandler EventHandler, Expression> 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) }); } } ///  /// Subscribe to changes in an object implementing INotifiyPropertyChanged. ///  ///  /// The object you are interested in. /// The property you are interested in. /// The delegate that will handle the event. public static void SubscribeToChange(this T ObjectThatNotifies, Expression> Property, PropertyChangedHandler 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); } }; } } 

Usado por ejemplo de esta manera:

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

Algunos errores de syntax en el ejemplo pueden existir. No lo probé. Pero deberías tener el concepto allí al menos 🙂

EDITAR: Ahora veo que es posible que haya deseado incluso menos trabajo, pero sí … las cosas de arriba al menos lo hacen mucho más fácil. Y evita todos los problemas aterradores al referirse a las propiedades que usan cadenas.

El Framework 4.5 nos proporciona el CallerMemberNameAttribute , que hace innecesario pasar el nombre de la propiedad como una cadena:

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

Similar a la solución de Svish, simplemente reemplazando la genialidad lambda con la aburrida funcionalidad de framework 😉

Si está trabajando en Framework 4.0 con KB2468871 instalado, puede instalar el paquete de compatibilidad de Microsoft BCL a través de nuget , que también proporciona este atributo.

Implementar un tipo seguro INotifyPropertyChanged : ver aquí

Luego crea tu propio fragmento de código:

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

¡Con el diseñador de fragmentos de código y lo has hecho! Manera fácil y segura de generar su INotifyPropertyChanged.

Puede tener un método de extensión en su delegado PropertyChanged y usarlo así:

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

Suscripción a un cambio de propiedad específico:

 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; 

El método de extensión puede determinar el nombre del remitente y de la propiedad simplemente inspeccionando el árbol de expresiones lambda y sin un gran impacto en el rendimiento :

 public static class PropertyChangedExtensions { public static void Raise( this PropertyChangedEventHandler handler, Expression> 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( this T obj, Expression> property, Action 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; } } 

Si el evento PropertyChanged se declara en un tipo base, entonces no será visible como un campo delegado en las clases derivadas. En este caso, una solución consiste en declarar un campo protegido de tipo PropertyChangedEventHandler e implementar explícitamente los accesos add y remove accessors:

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

No sé de ninguna manera estándar, pero sé dos soluciones:

1) PostSharp puede hacerlo por usted después de la comstackción. Es muy útil, pero toma algo de tiempo en cada comstackción.

2) Herramienta personalizada i Visual Studio. Puedes combinarlo con “clase parcial”. Luego puede crear una herramienta personalizada para su XML y puede generar código fuente desde el xml.

Por ejemplo este xml:

    

puede ser la fuente de este código:

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

es posible que desee examinar la Progtwigción Orientada a Aspectos como un todo

Frameworks => se puede ver en linfu

¿Podrías mirar a Castle o Spring.NET e implementar la funcionalidad del interceptor?

Mejora para llamar evento en clases de niños:

Llamado gracias a: this.NotifyPropertyChange (() => PageIndex);

Agregue esto en la clase NotificationExtensions:

  ///  /// Lève l'évènement de changement de valeur sur l'objet  /// pour la propriété utilisée dans la lambda . ///  /// L'objet portant la propriété et l'évènement. /// Une expression lambda utilisant la propriété subissant la modification. public static void NotifyPropertyChange(this INotifyPropertyChanged sender, Expression> 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) }); } } 

Acabo de encontrar ActiveSharp – Automatic INotifyPropertyChanged , todavía no lo he usado, pero se ve bien.

Para citar de su sitio web …


Envíe notificaciones de cambio de propiedad sin especificar el nombre de la propiedad como una cadena.

En cambio, escriba propiedades como esta:

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

Tenga en cuenta que no es necesario incluir el nombre de la propiedad como una cadena. ActiveSharp se da cuenta de ello de manera confiable y correcta. Funciona en función del hecho de que la implementación de su propiedad pasa el campo de respaldo (_foo) por ref. (ActiveSharp usa esa llamada "por ref" para identificar qué campo de respaldo se pasó, y desde el campo identifica la propiedad).

Solo para hacer la implementación más rápida , puedes usar un fragmento

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

las clases de proyectos de ViewModel que siguen el patrón MV-VM a menudo es necesario generar un evento “PropertyChanged” (para ayudar con la implementación de la interfaz INotifyPropertyChanged) desde el setter de una propiedad. Esta es una tarea tediosa que, con un poco de suerte, se resolverá algún día utilizando el comstackdor como servicio …

El núcleo del fragmento (para el cual el autor tiene todo el crédito , que no soy yo) es el siguiente

     

No existe una implementación única de Propiedad modificada que pueda manejar todas las formas en que las personas quieran usarla. La mejor apuesta es generar una clase de ayuda para hacer el trabajo por usted. Aquí hay un ejemplo de uno que uso

 ///  /// Helper Class that automates most of the actions required to implement INotifyPropertyChanged ///  public static class HPropertyChanged { private static Dictionary argslookup = new Dictionary(); public static string ThisPropertyName([CallerMemberName]string name = "") { return name; } public static string GetPropertyName(Expression> exp) { string rtn = ""; MemberExpression mex = exp.Body as MemberExpression; if(mex!=null) rtn = mex.Member.Name; return rtn; } public static void SetValue(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(ref T target, T newVal, Action 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]; } } 

editar: se sugirió que cambie de una clase auxiliar a una envoltura de valor y desde entonces he estado usando esta y me parece que funciona bastante bien

 public class NotifyValue { public static implicit operator T(NotifyValue 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() .Union( from d in dependenies.OfType() select new PropertyChangedEventArgs(d) ); } if (_propertyChanging != null) { _propertyChangingArg = dependenies.OfType() .Union( from d in dependenies.OfType() select new PropertyChangingEventArgs(d) ); } _PostChangeActions = dependenies.OfType(); } private T _Value; public T Value { get { return _Value; } set { SetValue(value); } } public bool SetValue(T value) { if (!EqualityComparer.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 _propertyChangedArg; private IEnumerable _propertyChangingArg; private IEnumerable _PostChangeActions; } 

ejemplo de uso

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

entonces en el constructor lo haces

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

Simplemente use este atributo encima de su statement automática de propiedad

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