Implementando INotifyPropertyChanged – ¿existe una mejor manera?

Microsoft debería haber implementado algo rápido para INotifyPropertyChanged , como en las propiedades automáticas, simplemente especifique {get; set; notify;} {get; set; notify;} {get; set; notify;} Creo que tiene mucho sentido hacerlo. ¿O hay complicaciones para hacerlo?

¿Podemos nosotros mismos implementar algo como ‘notificar’ en nuestras propiedades? ¿Existe una solución elegante para implementar INotifyPropertyChanged en su clase o la única forma de hacerlo es mediante el evento PropertyChanged en cada propiedad.

Si no, ¿podemos escribir algo para generar automáticamente la pieza de código para generar el evento PropertyChanged ?

Sin usar algo como postsharp, la versión mínima que uso utiliza algo como:

 public class Data : INotifyPropertyChanged { // boiler-plate public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField(ref T field, T value, string propertyName) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } // props private string name; public string Name { get { return name; } set { SetField(ref name, value, "Name"); } } } 

Cada propiedad es, entonces, algo así como:

  private string name; public string Name { get { return name; } set { SetField(ref name, value, "Name"); } } 

que no es enorme; también puede usarse como una clase base si lo desea. El bool return de SetField te dice si fue una SetField no SetField , en caso de que quieras aplicar otra lógica.


o incluso más fácil con C # 5:

 protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) {...} 

que se puede llamar así:

 set { SetField(ref name, value); } 

con el cual el comstackdor agregará el "Name" automáticamente.


C # 6.0 facilita la implementación:

 protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 

… y ahora con C # 7:

 private string name; public string Name { get => name; set => SetField(ref name, value); } 

A partir de .Net 4.5 finalmente hay una manera fácil de hacer esto.

.Net 4.5 presenta un nuevo atributo de información de llamada.

 private void OnPropertyChanged([CallerMemberName]string caller = null) { // make sure only to call this if the value actually changes var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(caller)); } } 

Probablemente sea una buena idea agregar un comparador a la función también.

 EqualityComparer.Default.Equals 

Más ejemplos aquí y aquí

Consulte también Información de llamada (C # y Visual Basic)

Realmente me gusta la solución de Marc, pero creo que se puede mejorar ligeramente para evitar el uso de una “cadena mágica” (que no admite la refactorización). En lugar de usar el nombre de la propiedad como una cadena, es fácil hacer que sea una expresión lambda:

 private string name; public string Name { get { return name; } set { SetField(ref name, value, () => Name); } } 

Simplemente agregue los siguientes métodos al código de Marc, hará el truco:

 protected virtual void OnPropertyChanged(Expression> selectorExpression) { if (selectorExpression == null) throw new ArgumentNullException("selectorExpression"); MemberExpression body = selectorExpression.Body as MemberExpression; if (body == null) throw new ArgumentException("The body must be a member expression"); OnPropertyChanged(body.Member.Name); } protected bool SetField(ref T field, T value, Expression> selectorExpression) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(selectorExpression); return true; } 

Por cierto, esto se inspiró en la URL actualizada de este blog

También está Fody que tiene un complemento PropertyChanged , que te permite escribir esto:

 [ImplementPropertyChanged] public class Person { public string GivenNames { get; set; } public string FamilyName { get; set; } } 

… y en tiempo de comstackción inyecta notificaciones de propiedad cambiadas.

Creo que la gente debería prestar más atención al rendimiento, realmente impacta en la UI cuando hay muchos objetos para enlazar (piense en una cuadrícula con más de 10.000 filas) o si el valor del objeto cambia frecuentemente (aplicación de monitoreo en tiempo real) .

Tomé varias implementaciones que se encuentran aquí y en otros lugares e hice una comparación, compruebalo la comparación de rendimiento de las implementaciones INotifyPropertyChanged .


Aquí hay un vistazo al resultado Implementación vs Tiempo de ejecución

Presento una clase de Bindable en mi blog en http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ Bindable usa un diccionario como una bolsa de propiedades. Es bastante fácil agregar las sobrecargas necesarias para que una subclase administre su propio campo de respaldo usando los parámetros de ref.

  • Sin cadena mágica
  • Sin reflection
  • Se puede mejorar para suprimir la búsqueda del diccionario por defecto

El código:

 public class Bindable : INotifyPropertyChanged { private Dictionary _properties = new Dictionary(); ///  /// Gets the value of a property ///  ///  ///  ///  protected T Get([CallerMemberName] string name = null) { Debug.Assert(name != null, "name != null"); object value = null; if (_properties.TryGetValue(name, out value)) return value == null ? default(T) : (T)value; return default(T); } ///  /// Sets the value of a property ///  ///  ///  ///  /// Use this overload when implicitly naming the property protected void Set(T value, [CallerMemberName] string name = null) { Debug.Assert(name != null, "name != null"); if (Equals(value, Get(name))) return; _properties[name] = value; OnPropertyChanged(name); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } 

Se puede usar así:

 public class Contact : Bindable { public string FirstName { get { return Get(); } set { Set(value); } } } 

Todavía no he tenido la oportunidad de probar esto yo mismo, pero la próxima vez que estoy configurando un proyecto con un gran requisito para INotifyPropertyChanged, tengo la intención de escribir un atributo Postsharp que inyectará el código en tiempo de comstackción. Algo como:

 [NotifiesChange] public string FirstName { get; set; } 

Se convertirá:

 private string _firstName; public string FirstName { get { return _firstname; } set { if (_firstname != value) { _firstname = value; OnPropertyChanged("FirstName") } } } 

No estoy seguro de si esto funcionará en la práctica y necesito sentarme y probarlo, pero no veo por qué no. Es posible que deba hacer que acepte algunos parámetros para situaciones en las que se necesita activar más de un OnPropertyChanged (si, por ejemplo, tengo una propiedad FullName en la clase anterior)

Actualmente estoy usando una plantilla personalizada en Resharper, pero incluso con eso me estoy hartando de que todas mis propiedades sean tan largas.


Ah, una búsqueda rápida en Google (que debería haber hecho antes de escribir esto) muestra que al menos una persona ha hecho algo así antes aquí . No es exactamente lo que tenía en mente, pero lo suficientemente cerca como para mostrar que la teoría es buena.

Sí, mejor manera existe ciertamente. Aquí está:

Tutorial paso a paso se redujo por mí, basado en este útil artículo .

  • Crear nuevo proyecto
  • Instalar el paquete de Castle Core en el proyecto

Install-Package Castle.Core

  • Instale mvvm light libraries solamente

Install-Package MvvmLightLibs

  • Agregue dos clases en el proyecto:

NotifierInterceptor

 public class NotifierInterceptor : IInterceptor { private PropertyChangedEventHandler handler; public static Dictionary _cache = new Dictionary(); public void Intercept(IInvocation invocation) { switch (invocation.Method.Name) { case "add_PropertyChanged": handler = (PropertyChangedEventHandler) Delegate.Combine(handler, (Delegate)invocation.Arguments[0]); invocation.ReturnValue = handler; break; case "remove_PropertyChanged": handler = (PropertyChangedEventHandler) Delegate.Remove(handler, (Delegate)invocation.Arguments[0]); invocation.ReturnValue = handler; break; default: if (invocation.Method.Name.StartsWith("set_")) { invocation.Proceed(); if (handler != null) { var arg = retrievePropertyChangedArg(invocation.Method.Name); handler(invocation.Proxy, arg); } } else invocation.Proceed(); break; } } private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName) { PropertyChangedEventArgs arg = null; _cache.TryGetValue(methodName, out arg); if (arg == null) { arg = new PropertyChangedEventArgs(methodName.Substring(4)); _cache.Add(methodName, arg); } return arg; } } 

ProxyCreator

 public class ProxyCreator { public static T MakeINotifyPropertyChanged() where T : class, new() { var proxyGen = new ProxyGenerator(); var proxy = proxyGen.CreateClassProxy( typeof(T), new[] { typeof(INotifyPropertyChanged) }, ProxyGenerationOptions.Default, new NotifierInterceptor() ); return proxy as T; } } 
  • Crea tu modelo de vista, por ejemplo:

-

  public class MainViewModel { public virtual string MainTextBox { get; set; } public RelayCommand TestActionCommand { get { return new RelayCommand(TestAction); } } public void TestAction() { Trace.WriteLine(MainTextBox); } } 
  • Poner enlaces en xaml:

       
  • Coloque la línea de código en el archivo de código subyacente MainWindow.xaml.cs de la siguiente manera:

DataContext = ProxyCreator.MakeINotifyPropertyChanged();

  • Disfrutar.

enter image description here

¡¡¡Atención!!! Todas las propiedades acotadas deben estar decoradas con la palabra clave virtual porque utilizan proxy de castillo para anular.

Un enfoque muy similar a AOP es inyectar el elemento INotifyPropertyChanged en un objeto ya instanciado sobre la marcha. Puedes hacer esto con algo como Castle DynamicProxy. Aquí hay un artículo que explica la técnica:

Agregar INotifyPropertyChanged a un objeto existente

Mira aquí: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

Está escrito en alemán, pero puedes descargar ViewModelBase.cs. Todos los comentarios en el archivo cs están escritos en inglés.

Con esta ViewModelBase-Class es posible implementar propiedades enlazables similares a las bien conocidas Propiedades de dependencia:

 public string SomeProperty { get { return GetValue( () => SomeProperty ); } set { SetValue( () => SomeProperty, value ); } } 

Basado en la respuesta de Thomas, que fue adaptada de una respuesta de Marc, convertí el código de propiedad reflejada en una clase base:

 public abstract class PropertyChangedBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } protected void OnPropertyChanged(Expression> selectorExpression) { if (selectorExpression == null) throw new ArgumentNullException("selectorExpression"); var me = selectorExpression.Body as MemberExpression; // Nullable properties can be nested inside of a convert function if (me == null) { var ue = selectorExpression.Body as UnaryExpression; if (ue != null) me = ue.Operand as MemberExpression; } if (me == null) throw new ArgumentException("The body must be a member expression"); OnPropertyChanged(me.Member.Name); } protected void SetField(ref T field, T value, Expression> selectorExpression, params Expression>[] additonal) { if (EqualityComparer.Default.Equals(field, value)) return; field = value; OnPropertyChanged(selectorExpression); foreach (var item in additonal) OnPropertyChanged(item); } } 

El uso es el mismo que la respuesta de Thomas, excepto que puede pasar propiedades adicionales para notificar. Esto fue necesario para manejar columnas calculadas que necesitan actualizarse en una grilla.

 private int _quantity; private int _price; public int Quantity { get { return _quantity; } set { SetField(ref _quantity, value, () => Quantity, () => Total); } } public int Price { get { return _price; } set { SetField(ref _price, value, () => Price, () => Total); } } public int Total { get { return _price * _quantity; } } 

Tengo esto impulsando una colección de elementos almacenados en una BindingList expuesta a través de un DataGridView. Ha eliminado la necesidad de que realice llamadas de Refresh () manuales a la grilla.

Permítanme presentar mi propio enfoque llamado Yappi . Pertenece a los generadores de clases derivados de Runtime | proxy, agregando nuevas funcionalidades a un objeto o tipo existente, como Dynamic Proxy de Caste Project.

Permite implementar INotifyPropertyChanged una vez en la clase base, y luego declarar las clases derivadas en el siguiente estilo, aún admitiendo INotifyPropertyChanged para las nuevas propiedades:

 public class Animal:Concept { protected Animal(){} public virtual string Name { get; set; } public virtual int Age { get; set; } } 

La complejidad de la construcción derivada de clase o proxy se puede ocultar detrás de la siguiente línea:

 var animal = Concept.Create.New(); 

Y todo el trabajo de implementación de INotifyPropertyChanged se puede hacer así:

 public class Concept:INotifyPropertyChanged { //Hide constructor protected Concept(){} public static class Create where TConcept:Concept { //Construct derived Type calling PropertyProxy.ConstructType public static readonly Type Type = PropertyProxy.ConstructType>(new Type[0], true); //Create constructing delegate calling Constructor.Compile public static Func New = Constructor.Compile>(Type); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs) { var caller = PropertyChanged; if(caller!=null) { caller(this, eventArgs); } } //define implementation public class Implementation : DefaultImplementation where TConcept:Concept { public override Func OverrideGetter(PropertyInfo property) { return PropertyImplementation.GetGetter(property.Name); } ///  /// Overriding property setter implementation. ///  /// Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType. /// Type, declaring property. /// Constructed type. TConstructedType is TDeclaringType and TBaseType. /// Type of property. /// PropertyInfo of property. /// Delegate, corresponding to property setter implementation. public override Action OverrideSetter(PropertyInfo property) { //This code called once for each declared property on derived type's initialization. //EventArgs instance is shared between all events for each concrete property. var eventArgs = new PropertyChangedEventArgs(property.Name); //get delegates for base calls. Action setter = PropertyImplementation.GetSetter(property.Name); Func getter = PropertyImplementation.GetGetter(property.Name); var comparer = EqualityComparer.Default; return (pthis, value) => {//This code executes each time property setter is called. if (comparer.Equals(value, getter(pthis))) return; //base. call setter(pthis, value); //Directly accessing Concept's protected method. pthis.OnPropertyChanged(eventArgs); }; } } } 

Es completamente seguro para la refactorización, no utiliza reflexión después de la construcción del tipo y lo suficientemente rápido.

Todas estas respuestas son muy buenas.

Mi solución es usar los fragmentos de código para hacer el trabajo.

Utiliza la llamada más simple al evento PropertyChanged.

Guarde este fragmento y úselo mientras usa el fragmento ‘fullprop’.

la ubicación se puede encontrar en el menú ‘Tools \ Code Snippet Manager …’ en Visual Studio.

    
inotifypropfull inotifypropfull http://ofirzeitoun.wordpress.com/ Code snippet for property and backing field with notification Ofir Zeitoun Expansion
type Property type int property Property name MyProperty field The variable backing this property myVar

Puede modificar la llamada como lo desee (para usar las soluciones anteriores)

Si usa dinámicas en .NET 4.5, no necesita preocuparse por INotifyPropertyChanged .

 dynamic obj = new ExpandoObject(); obj.Name = "John"; 

si el nombre está vinculado a algún control, simplemente funciona bien.

Creé un Método de extensión en mi biblioteca base para su reutilización:

 public static class INotifyPropertyChangedExtensions { public static bool SetPropertyAndNotify(this INotifyPropertyChanged sender, PropertyChangedEventHandler handler, ref T field, T value, [CallerMemberName] string propertyName = "", EqualityComparer equalityComparer = null) { bool rtn = false; var eqComp = equalityComparer ?? EqualityComparer.Default; if (!eqComp.Equals(field,value)) { field = value; rtn = true; if (handler != null) { var args = new PropertyChangedEventArgs(propertyName); handler(sender, args); } } return rtn; } } 

Esto funciona con .Net 4.5 debido a CallerMemberNameAttribute . Si desea usarlo con una versión .Net anterior, debe cambiar la statement de método de: ...,[CallerMemberName] string propertyName = "", ... to ...,string propertyName, ...

Uso:

 public class Dog : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; string _name; public string Name { get { return _name; } set { this.SetPropertyAndNotify(PropertyChanged, ref _name, value); } } } 

Guardo esto como un fragmento. C # 6 agrega una buena syntax para invocar al controlador.

 // INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void Set(ref T property, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(property, value) == false) { property = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } 

Aquí hay una versión de Unity3D o no de CallerMemberName de NotifyPropertyChanged

 public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged { private readonly Dictionary _properties = new Dictionary(); private static readonly StackTrace stackTrace = new StackTrace(); public event PropertyChangedEventHandler PropertyChanged; ///  /// Resolves a Property's name from a Lambda Expression passed in. ///  ///  ///  ///  internal string GetPropertyName(Expression> property) { var expression = (MemberExpression) property.Body; var propertyName = expression.Member.Name; Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!"); return propertyName; } #region Notification Handlers ///  /// Notify's all other objects listening that a value has changed for nominated propertyName ///  ///  internal void NotifyOfPropertyChange(string propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } ///  /// Notifies subscribers of the property change. ///  /// The type of the property. /// The property expression. internal void NotifyOfPropertyChange(Expression> property) { var propertyName = GetPropertyName(property); NotifyOfPropertyChange(propertyName); } ///  /// Raises the  event directly. ///  /// The  instance containing the event data. internal void OnPropertyChanged(PropertyChangedEventArgs e) { var handler = PropertyChanged; if (handler != null) { handler(this, e); } } #endregion #region Getters ///  /// Gets the value of a property ///  ///  ///  ///  internal T Get(Expression> property) { var propertyName = GetPropertyName(property); return Get(GetPropertyName(property)); } ///  /// Gets the value of a property automatically based on its caller. ///  ///  ///  internal T Get() { var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name; return Get(name); } ///  /// Gets the name of a property based on a string. ///  ///  ///  ///  internal T Get(string name) { object value = null; if (_properties.TryGetValue(name, out value)) return value == null ? default(T) : (T) value; return default(T); } #endregion #region Setters ///  /// Sets the value of a property whilst automatically looking up its caller name. ///  ///  ///  internal void Set(T value) { var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name; Set(value, propertyName); } ///  /// Sets the value of a property ///  ///  ///  ///  internal void Set(T value, string propertyName) { Debug.Assert(propertyName != null, "name != null"); if (Equals(value, Get(propertyName))) return; _properties[propertyName] = value; NotifyOfPropertyChange(propertyName); } ///  /// Sets the value of a property based off an Expression (()=>FieldName) ///  ///  ///  ///  internal void Set(T value, Expression> property) { var propertyName = GetPropertyName(property); Debug.Assert(propertyName != null, "name != null"); if (Equals(value, Get(propertyName))) return; _properties[propertyName] = value; NotifyOfPropertyChange(propertyName); } #endregion } 

This code enables you to write property backing fields like this:

  public string Text { get { return Get(); } set { Set(value); } } 

Furthermore, in resharper if you create a pattern/search snippet you can then also automate you’re workflow by converting simple prop fields into the above backing.

Search Pattern:

 public $type$ $fname$ { get; set; } 

Replace Pattern:

 public $type$ $fname$ { get { return Get<$type$>(); } set { Set(value); } } 

I have written an article that helps with this ( https://msdn.microsoft.com/magazine/mt736453 ). You can use the SolSoft.DataBinding NuGet package. Then you can write code like this:

 public class TestViewModel : IRaisePropertyChanged { public TestViewModel() { this.m_nameProperty = new NotifyProperty(this, nameof(Name), null); } private readonly NotifyProperty m_nameProperty; public string Name { get { return m_nameProperty.Value; } set { m_nameProperty.SetValue(value); } } // Plus implement IRaisePropertyChanged (or extend BaseViewModel) } 

Beneficios:

  1. base class is optional
  2. no reflection on every ‘set value’
  3. can have properties that depend on other properties, and they all automatically raise the appropriate events (article has an example of this)

Other things you may want to consider when implementing these sorts of properties is the fact that the INotifyPropertyChang *ed *ing both use event argument classes.

If you have a large number of properties that are being set then the number of event argument class instances can be huge, you should consider caching them as they are one of the areas that a string explosion can occur.

Take a look at this implementation and explanation of why it was conceived.

Josh Smiths Blog

An idea using reflection:

 class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; bool Notify(MethodBase mb, ref T oldValue, T newValue) { // Get Name of Property string name = mb.Name.Substring(4); // Detect Change bool changed = EqualityComparer.Default.Equals(oldValue, newValue); // Return if no change if (!changed) return false; // Update value oldValue = newValue; // Raise Event if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); }//if // Notify caller of change return true; }//method string name; public string Name { get { return name; } set { Notify(MethodInfo.GetCurrentMethod(), ref this.name, value); } }//method }//class 

Another combined solution is using StackFrame:

 public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void Set(ref T field, T value) { MethodBase method = new StackFrame(1).GetMethod(); field = value; Raise(method.Name.Substring(4)); } protected void Raise(string propertyName) { var temp = PropertyChanged; if (temp != null) { temp(this, new PropertyChangedEventArgs(propertyName)); } } } 

Uso:

 public class TempVM : BaseViewModel { private int _intP; public int IntP { get { return _intP; } set { Set(ref _intP, value); } } } 

I realize this question already has a gazillion answers, but none of them felt quite right for me. My issue is I don’t want any performance hits and am willing to put up with a little verbosity for that reason alone. I also don’t care too much for auto properties either, which led me to the following solution:

 public abstract class AbstractObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected virtual bool SetValue(ref TKind Source, TKind NewValue, params string[] Notify) { //Set value if the new value is different from the old if (!Source.Equals(NewValue)) { Source = NewValue; //Notify all applicable properties foreach (var i in Notify) OnPropertyChanged(i); return true; } return false; } public AbstractObject() { } } 

In other words, the above solution is convenient if you don’t mind doing this:

 public class SomeObject : AbstractObject { public string AnotherProperty { get { return someProperty ? "Car" : "Plane"; } } bool someProperty = false; public bool SomeProperty { get { return someProperty; } set { SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty"); } } public SomeObject() : base() { } } 

Pros

  • No reflection
  • Only notifies if old value != new value
  • Notify multiple properties at once

Contras

  • No auto properties (you can add support for both, though!)
  • Some verbosity
  • Boxing (small performance hit?)

Alas, it is still better than doing this,

 set { if (!someProperty.Equals(value)) { someProperty = value; OnPropertyChanged("SomeProperty"); OnPropertyChanged("AnotherProperty"); } } 

For every single property, which becomes a nightmare with the additional verbosity ;-(

Note, I do not claim this solution is better performance-wise compared to the others, just that it is a viable solution for those who don’t like the other solutions presented.

I came up with this base class to implement the observable pattern, pretty much does what you need ( “automatically” implementing the set and get). I spent line an hour on this as prototype, so it doesn’t have many unit tests, but proves the concept. Note it uses the Dictionary to remove the need for private fields.

  public class ObservableByTracking : IObservable { private readonly Dictionary _expando; private bool _isDirty; public ObservableByTracking() { _expando = new Dictionary(); var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList(); foreach (var property in properties) { var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType) { Value = GetDefault(property.PropertyType) }; _expando[BuildKey(valueContext)] = valueContext; } } protected void SetValue(Expression> expression, T value) { var keyContext = GetKeyContext(expression); var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType); if (!_expando.ContainsKey(key)) { throw new Exception($"Object doesn't contain {keyContext.PropertyName} property."); } var originalValue = (T)_expando[key].Value; if (EqualityComparer.Default.Equals(originalValue, value)) { return; } _expando[key].Value = value; _isDirty = true; } protected T GetValue(Expression> expression) { var keyContext = GetKeyContext(expression); var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType); if (!_expando.ContainsKey(key)) { throw new Exception($"Object doesn't contain {keyContext.PropertyName} property."); } var value = _expando[key].Value; return (T)value; } private KeyContext GetKeyContext(Expression> expression) { var castedExpression = expression.Body as MemberExpression; if (castedExpression == null) { throw new Exception($"Invalid expression."); } var parameterName = castedExpression.Member.Name; var propertyInfo = castedExpression.Member as PropertyInfo; if (propertyInfo == null) { throw new Exception($"Invalid expression."); } return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName}; } private static string BuildKey(ObservablePropertyContext observablePropertyContext) { return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}"; } private static string BuildKey(string parameterName, Type type) { return $"{type.Name}.{parameterName}"; } private static object GetDefault(Type type) { if (type.IsValueType) { return Activator.CreateInstance(type); } return null; } public bool IsDirty() { return _isDirty; } public void SetPristine() { _isDirty = false; } private class KeyContext { public string PropertyName { get; set; } public Type PropertyType { get; set; } } } public interface IObservable { bool IsDirty(); void SetPristine(); } 

Here’s the usage

 public class ObservableByTrackingTestClass : ObservableByTracking { public ObservableByTrackingTestClass() { StringList = new List(); StringIList = new List(); NestedCollection = new List(); } public IEnumerable StringList { get { return GetValue(() => StringList); } set { SetValue(() => StringIList, value); } } public IList StringIList { get { return GetValue(() => StringIList); } set { SetValue(() => StringIList, value); } } public int IntProperty { get { return GetValue(() => IntProperty); } set { SetValue(() => IntProperty, value); } } public ObservableByTrackingTestClass NestedChild { get { return GetValue(() => NestedChild); } set { SetValue(() => NestedChild, value); } } public IList NestedCollection { get { return GetValue(() => NestedCollection); } set { SetValue(() => NestedCollection, value); } } public string StringProperty { get { return GetValue(() => StringProperty); } set { SetValue(() => StringProperty, value); } } } 

I have just found ActiveSharp – Automatic INotifyPropertyChanged , I have yet to use it, but it looks good.

To quote from it’s web site…


Send property change notifications without specifying property name as a string.

Instead, write properties like this:

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

Note that there is no need to include the name of the property as a string. ActiveSharp reliably and correctly figures that out for itself. It works based on the fact that your property implementation passes the backing field (_foo) by ref. (ActiveSharp uses that "by ref" call to identify which backing field was passed, and from the field it identifies the property).

Another Idea…

  public class ViewModelBase : INotifyPropertyChanged { private Dictionary _propertyStore = new Dictionary(); protected virtual void SetValue(T value, [CallerMemberName] string propertyName="") { _propertyStore[propertyName] = value; OnPropertyChanged(propertyName); } protected virtual T GetValue([CallerMemberName] string propertyName = "") { object ret; if (_propertyStore.TryGetValue(propertyName, out ret)) { return (T)ret; } else { return default(T); } } //Usage //public string SomeProperty { // get { return GetValue(); } // set { SetValue(value); } //} public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { var temp = PropertyChanged; if (temp != null) temp.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } 

=> here my solution with the following features

  public ResourceStatus Status { get { return _status; } set { _status = value; Notify(Npcea.Status,Npcea.Comments); } } 
  1. no refelction
  2. short notation
  3. no magic string in your business code
  4. Reusability of PropertyChangedEventArgs across application
  5. Possibility to notify multiple properties in one statement

Utilizar esta

 using System; using System.ComponentModel; using System.Reflection; using System.Reflection.Emit; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Proxies; public static class ObservableFactory { public static T Create(T target) { if (!typeof(T).IsInterface) throw new ArgumentException("Target should be an interface", "target"); var proxy = new Observable(target); return (T)proxy.GetTransparentProxy(); } } internal class Observable : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging { private readonly T target; internal Observable(T target) : base(ImplementINotify(typeof(T))) { this.target = target; } public override IMessage Invoke(IMessage msg) { var methodCall = msg as IMethodCallMessage; if (methodCall != null) { return HandleMethodCall(methodCall); } return null; } public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; IMessage HandleMethodCall(IMethodCallMessage methodCall) { var isPropertySetterCall = methodCall.MethodName.StartsWith("set_"); var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null; if (isPropertySetterCall) { OnPropertyChanging(propertyName); } try { object methodCalltarget = target; if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"|| methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging") { methodCalltarget = this; } var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs); if (isPropertySetterCall) { OnPropertyChanged(methodCall.MethodName.Substring(4)); } return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); } catch (TargetInvocationException invocationException) { var exception = invocationException.InnerException; return new ReturnMessage(exception, methodCall); } } protected virtual void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } protected virtual void OnPropertyChanging(string propertyName) { var handler = PropertyChanging; if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName)); } public static Type ImplementINotify(Type objectType) { var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString()); var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly( tempAssemblyName, AssemblyBuilderAccess.RunAndCollect); var moduleBuilder = dynamicAssembly.DefineDynamicModule( tempAssemblyName.Name, tempAssemblyName + ".dll"); var typeBuilder = moduleBuilder.DefineType( objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract); typeBuilder.AddInterfaceImplementation(objectType); typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged)); typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging)); var newType = typeBuilder.CreateType(); return newType; } } 

}

I resolved in This Way (it’s a little bit laboriouse, but it’s surely the faster in runtime).

In VB (sorry, but I think it’s not hard translate it in C#), I make this substitution with RE:

 (?<(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?(Public|Private|Friend|Protected) .*Property )(?[^ ]*) As (?.*?)[ |\r\n](?![ |\r\n]*Get) 

con:

 Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n 

This transofrm all code like this:

  Protected Friend Property StartDate As DateTime? 

En

 Private _StartDate As DateTime?  Protected Friend Property StartDate As DateTime? Get Return _StartDate End Get Set(Value As DateTime?) If _StartDate <> Value Then _StartDate = Value RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate")) End If End Set End Property 

And If I want to have a more readable code, I can be the opposite just making the following substitution:

 Private _(?.*) As (?.*)[\r\n ]*(?<(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?(Public|Private|Friend|Protected) .*Property )\k As \k[\r\n ]*Get[\r\n ]*Return _\k[\r\n ]*End Get[\r\n ]*Set\(Value As \k\)[\r\n ]*If _\k <> Value Then[\r\n ]*_\k = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property 

Con

 ${Attr} ${Def} ${Name} As ${Type} 

I throw to replace the IL code of the set method, but I can’t write a lot of compiled code in IL… If a day I write it, I’ll say you!

I use the following extension method (using C# 6.0) to make the INPC implemenation as easy as possible:

 public static bool ChangeProperty(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender, IEqualityComparer comparer = null, [CallerMemberName] string propertyName = null) { if (comparer == null) comparer = EqualityComparer.Default; if (comparer.Equals(field, value)) { return false; } else { field = value; propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName)); return true; } } 

The INPC implementation boils down to (you can either implement this every time or create a base class):

 public class INPCBaseClass: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool changeProperty(ref T field, T value, IEqualityComparer comparer = null, [CallerMemberName] string propertyName = null) { return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName); } } 

Then write your properties like this:

 private string testProperty; public string TestProperty { get { return testProperty; } set { changeProperty(ref testProperty, value); } } 

NOTE: You can omit the [CallerMemberName] declaration in the extension method, if you want, but I wanted to keep it flexible.

If you have properties without a backing field you can overload changeProperty :

 protected bool changeProperty(T property, Action set, T value, IEqualityComparer comparer = null, [CallerMemberName] string propertyName = null) { bool ret = changeProperty(ref property, value, comparer, propertyName); if (ret) set(property); return ret; } 

An example use would be:

 public string MyTestProperty { get { return base.TestProperty; } set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); } } 

Prism 5 implementation:

 public abstract class BindableBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (object.Equals(storage, value)) return false; storage = value; this.OnPropertyChanged(propertyName); return true; } protected void OnPropertyChanged(string propertyName) { var eventHandler = this.PropertyChanged; if (eventHandler != null) { eventHandler(this, new PropertyChangedEventArgs(propertyName)); } } protected void OnPropertyChanged(Expression> propertyExpression) { var propertyName = PropertySupport.ExtractPropertyName(propertyExpression); this.OnPropertyChanged(propertyName); } } public static class PropertySupport { public static string ExtractPropertyName(Expression> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } var memberExpression = propertyExpression.Body as MemberExpression; if (memberExpression == null) { throw new ArgumentException("The expression is not a member access expression.", "propertyExpression"); } var property = memberExpression.Member as PropertyInfo; if (property == null) { throw new ArgumentException("The member access expression does not access a property.", "propertyExpression"); } var getMethod = property.GetMethod; if (getMethod.IsStatic) { throw new ArgumentException("The referenced property is a static property.", "propertyExpression"); } return memberExpression.Member.Name; } }