onpropertychanged - raisepropertychanged c#




Implementando INotifyPropertyChanged-¿existe una mejor manera? (20)

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

.Net 4.5 introduce un nuevo atributo de información del llamante.

private void OnPropertyChanged<T>([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 también un comparador a la función.

EqualityComparer<T>.Default.Equals

Más ejemplos here y here

También vea la información de la persona que llama (C # y Visual Basic)

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 alguna complicación 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 elevar el evento PropertyChanged en cada propiedad?

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


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

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> 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

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

Este código le permite escribir campos de respaldo de propiedad como este:

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

Además, en resharper, si creas un fragmento de patrón / búsqueda, también puedes automatizar tu flujo de trabajo al convertir campos de apoyo simples en el respaldo anterior.

Patrón de búsqueda:

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

Reemplazar patrón:

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

Creé un método de extensión en mi biblioteca base para reutilizarlo:

public static class INotifyPropertyChangedExtensions
{
    public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
               PropertyChangedEventHandler handler, ref T field, T value, 
               [CallerMemberName] string propertyName = "",
               EqualityComparer<T> equalityComparer = null)
    {
        bool rtn = false;
        var eqComp = equalityComparer ?? EqualityComparer<T>.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 anterior de .Net, debe cambiar la declaración del 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);
        }
    }
}

Creo que la gente debería prestar un poco más de atención al rendimiento, realmente afecta la interfaz de usuario cuando hay muchos objetos para vincular (piense en una cuadrícula con más de 10,000 filas) o si el valor del objeto cambia con frecuencia (aplicación de monitoreo en tiempo real) .

Tomé varias implementaciones encontradas aquí y en otros lugares e hice una comparación, verifíquelas en la comparación de rendimiento de las implementaciones de INotifyPropertyChanged .

Aquí hay un vistazo al resultado.


Mantengo esto como un fragmento. C # 6 agrega una buena sintaxis para invocar el controlador.

// INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

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

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

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

Con esta clase ViewModelBase es posible implementar propiedades vinculables similares a las conocidas propiedades de dependencia:

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

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 convertirlo en una expresión lambda:

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

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

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> 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<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

Por cierto, esto fue inspirado por este blog actualizado URL


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

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

  • Crear nuevo proyecto
  • Instalar el paquete del núcleo del castillo en el proyecto

Instalar el paquete Castle.Core

  • Instalar solo bibliotecas de mvvm light

Install-Package MvvmLightLibs

  • Agrega dos clases en el proyecto:

NotifierInterceptor

public class NotifierInterceptor : IInterceptor
    {
        private PropertyChangedEventHandler handler;
        public static Dictionary<String, PropertyChangedEventArgs> _cache =
          new Dictionary<string, PropertyChangedEventArgs>();

        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<T>() 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 los enlaces en xaml:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
    
  • Coloque la línea de código en el archivo de código subyacente MainWindow.xaml.cs así:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Disfrutar.

¡¡¡Atención!!! Todas las propiedades delimitadas se deben decorar con la palabra clave virtual porque se usaron por proxy de castillo para anular.


Sin usar algo como postsharp, la versión mínima que uso usa 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<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.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 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 clase base si lo desea. El retorno de bool de SetField te dice si fue un no-op, en caso de que quieras aplicar otra lógica.

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

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

que se puede llamar así:

set { SetField(ref name, value); }

Con lo cual el compilador agregará automáticamente el "Name" .

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

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 el momento de la compilación inyecta propiedades modificadas notificaciones.


Todavía no he tenido la oportunidad de probar esto, pero la próxima vez que esté configurando un proyecto con un gran requisito para INotifyPropertyChanged, pretendo escribir un atributo Postsharp que inyectará el código en tiempo de compilació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 debe activar más de un OnPropertyChanged (si, por ejemplo, tuviera una propiedad FullName en la clase anterior)

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

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


Un enfoque muy similar a AOP es inyectar el material 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


Otra idea...

 public class ViewModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
    protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
        _propertyStore[propertyName] = value;
        OnPropertyChanged(propertyName);
    }
    protected virtual T GetValue<T>([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<string>();  }
    //    set { SetValue(value); }
    //}

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Otra solución combinada es usar StackFrame:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(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<int>(ref _intP, value); }
    }
}

Resolví de esta manera (es un poco laborioso, pero seguramente es más rápido en tiempo de ejecución).

En VB (lo siento, pero creo que no es difícil traducirlo en C #), hago esta sustitución con RE:

(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\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

Esta transofrm todo el código de esta manera:

<Bindable(True)>
Protected Friend Property StartDate As DateTime?

En

Private _StartDate As DateTime?
<Bindable(True)>
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

Y si quiero tener un código más legible, puedo ser lo contrario haciendo la siguiente sustitución:

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

Con

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

Tiro para reemplazar el código IL del método establecido, pero no puedo escribir un montón de código compilado en IL ... ¡Si un día lo escribo, te lo diré!


Una idea utilizando la reflexión:

class ViewModelBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {

        // Get Name of Property
        string name = mb.Name.Substring(4);

        // Detect Change
        bool changed = EqualityComparer<T>.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

=> here mi solución con las siguientes características

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. no refelcción
  2. notación corta
  3. ninguna cadena mágica en tu código de negocio
  4. Reutilización de PropertyChangedEventArgs en toda la aplicación
  5. Posibilidad de notificar múltiples propiedades en una declaración

Acabo de encontrar ActiveSharp - Automatic INotifyPropertyChanged , todavía tengo que usarlo, pero se ve bien.

Para citar desde su sitio web ...

Enviar notificaciones de cambio de propiedad sin especificar el nombre de la propiedad como una cadena.

En su lugar, escribe 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 lo determina de manera confiable y correcta por sí mismo. 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 referencia" para identificar qué campo de respaldo se pasó, y desde el campo identifica la propiedad).


Me doy cuenta de que esta pregunta ya tiene un millón de respuestas, pero ninguna de ellas me pareció adecuada. Mi problema es que no quiero ningún éxito de rendimiento y estoy dispuesto a soportar un poco de verbosidad solo por esa razón. Tampoco me importan demasiado las propiedades automáticas, lo que me llevó a la siguiente solución:

public abstract class AbstractObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetValue<TKind>(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()
    {
    }
}

En otras palabras, la solución anterior es conveniente si no te importa hacer esto:

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

  • Sin reflejo
  • Solo notifica si valor antiguo! = Valor nuevo
  • Notificar múltiples propiedades a la vez

Contras

  • Sin propiedades automáticas (aunque puede agregar soporte para ambas)
  • Algo de verbosidad
  • Boxeo (pequeño éxito de rendimiento?)

Ay, todavía es mejor que hacer esto,

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

Por cada propiedad individual, que se convierte en una pesadilla con la verbosidad adicional ;-(

Tenga en cuenta que no pretendo que esta solución sea mejor en términos de rendimiento en comparación con las demás, solo que es una solución viable para aquellos a quienes no les gustan las otras soluciones presentadas.


Se me ocurrió esta clase base para implementar el patrón observable, hace prácticamente lo que necesitas ( "automáticamente" implementando el conjunto y obteniendo). Pasé una hora en línea como prototipo, por lo que no tiene muchas pruebas unitarias, pero prueba el concepto. Tenga en cuenta que utiliza Dictionary<string, ObservablePropertyContext>para eliminar la necesidad de campos privados.

  public class ObservableByTracking<T> : IObservable<T>
  {
    private readonly Dictionary<string, ObservablePropertyContext> _expando;
    private bool _isDirty;

    public ObservableByTracking()
    {
      _expando = new Dictionary<string, ObservablePropertyContext>();

      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<T>(Expression<Func<T>> 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<T>.Default.Equals(originalValue, value))
      {
        return;
      }

      _expando[key].Value = value;
      _isDirty = true;
    }

    protected T GetValue<T>(Expression<Func<T>> 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<T>(Expression<Func<T>> 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<T>
  {
    bool IsDirty();
    void SetPristine();
  }

Aquí está el uso

public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
  {
    public ObservableByTrackingTestClass()
    {
      StringList = new List<string>();
      StringIList = new List<string>();
      NestedCollection = new List<ObservableByTrackingTestClass>();
    }

    public IEnumerable<string> StringList
    {
      get { return GetValue(() => StringList); }
      set { SetValue(() => StringIList, value); }
    }

    public IList<string> 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<ObservableByTrackingTestClass> NestedCollection
    {
      get { return GetValue(() => NestedCollection); }
      set { SetValue(() => NestedCollection, value); }
    }

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




inotifypropertychanged