c# raisepropertychanged Implementando INotifyPropertyChanged-¿existe una mejor manera?




raisepropertychanged c# (24)

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

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

Otras cosas que puede considerar al implementar este tipo de propiedades es el hecho de que INotifyPropertyChang * ed * ing utiliza clases de argumento de evento.

Si tiene una gran cantidad de propiedades que se están configurando, entonces el número de instancias de la clase de argumento de evento puede ser enorme, debe considerar el almacenamiento en caché ya que son una de las áreas en las que puede ocurrir una explosión de cadena.

Eche un vistazo a esta implementación y explicación de por qué fue concebida.

Blog de Josh Smiths


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.


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

Uso el siguiente método de extensión (usando C # 6.0) para hacer que la implementación de INPC sea lo más fácil posible:

public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    if (comparer == null)
        comparer = EqualityComparer<T>.Default;

    if (comparer.Equals(field, value))
    {
        return false;
    }
    else
    {
        field = value;
        propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
        return true;
    }
}

La implementación de INPC se reduce a (puede implementar esto cada vez o crear una clase base):

public class INPCBaseClass: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool changeProperty<T>(ref T field, T value,
        IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
    {
        return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName);
    }
}

Luego escribe tus propiedades de esta manera:

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

NOTA: Puede omitir la [CallerMemberName]declaración en el método de extensión, si lo desea, pero quería mantenerla flexible.

Si tiene propiedades sin un campo de respaldo, puede sobrecargar changeProperty:

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

Un ejemplo de uso sería:

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

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.


Si está utilizando dinámica 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.


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

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


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

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

Todas estas respuestas son muy bonitas.

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

Esto utiliza la llamada más simple al evento PropertyChanged.

Guarde este fragmento de código y utilícelo cuando utilice el fragmento de código 'fullprop'.

la ubicación se puede encontrar en el menú 'Herramientas \ Code Snippet Manager ...' en Visual Studio.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>inotifypropfull</Title>
            <Shortcut>inotifypropfull</Shortcut>
            <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
            <Description>Code snippet for property and backing field with notification</Description>
            <Author>Ofir Zeitoun</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set { 
            $field$ = value;
            var temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs("$property$"));
            }
        }
    }
    $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

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


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

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


=> 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

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

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.


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)


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.


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

  • Sin cuerda magica
  • Sin reflejo
  • Se puede mejorar para suprimir la búsqueda de diccionario predeterminada

El código:

public class Bindable : INotifyPropertyChanged {
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    /// <summary>
    /// Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    protected T Get<T>([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);
    }

    /// <summary>
    /// Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <remarks>Use this overload when implicitly naming the property</remarks>
    protected void Set<T>(T value, [CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(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<string>(); }
        set { Set(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.


Basado en la respuesta de Thomas que fue adaptada de una respuesta de Marc, he convertido el código de cambio de propiedad reflejante 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<T>(Expression<Func<T>> 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<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
    {
        if (EqualityComparer<T>.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 deben actualizarse en una cuadrícula.

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 conduciendo una colección de artículos almacenados en un BindingList expuesto a través de un DataGridView. Se ha eliminado la necesidad de que yo haga llamadas de actualización manual () a la cuadrícula.


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





inotifypropertychanged