Implementando INotifyPropertyChanged: ¿existe una mejor manera?

647

Microsoft debería haber implementado algo rápido INotifyPropertyChanged, como en las propiedades automáticas, solo especifique {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 INotifyPropertyChangeden su clase o la única forma de hacerlo es elevando el PropertyChangedevento en cada propiedad?

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

PK
fuente
77
code.google.com/p/notifypropertyweaver puede ser útil
Ian Ringrose
77
el enlace de arriba está muerto. github.com/SimonCropp/NotifyPropertyWeaver
prime23
2
En su lugar, puede usar DependencyObject y DependencyProperties. ¡DECIR AH! Hice un gracioso
Phil
66
@ joao2fast4u github.com/Fody/PropertyChanged/wiki/…
prime23
55
En ese momento, no era posible realizar cambios en C #, dado que teníamos un gran historial de interdependencias. Entonces, cuando nació MVVM, supongo, simplemente no nos esforzamos mucho para resolver este problema y sé que el equipo de Patrones y Prácticas tuvo algunas dificultades en el camino (por lo tanto, también obtuviste MEF como parte de eso hilo de investigación). Hoy creo que [CallerMemberName] es la respuesta a lo anterior.
Scott Barnes

Respuestas:

633

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 así como:

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

que no es enorme; También se puede utilizar como clase base si lo desea. El boolretorno de SetFieldte 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 el cual el compilador 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:

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}
Marc Gravell
fuente
44
Buen truco Marc! Sugerí una mejora para usar una expresión lambda en lugar del nombre de la propiedad, vea mi respuesta
Thomas Levesque
77
@Thomas: la lambda está muy bien, pero agrega mucha sobrecarga para algo que en realidad es muy simple. Un truco útil, pero no estoy seguro de que siempre sea práctico.
Marc Gravell
14
@Marc - Sí, se puede degradar el rendimiento probablemente ... Sin embargo, me gusta mucho el hecho de que está controlada en tiempo de compilación, y se refactorizado correctamente por el comando "Cambiar nombre"
Thomas Levesque
44
@Gusdor, afortunadamente, con C # 5 no hay necesidad de comprometerse: puede obtener lo mejor de ambos a través (como señala Pedro77)[CallerMemberName]
Marc Gravell
44
@Gusdor el lenguaje y el marco están separados; puede usar el compilador C # 5, apuntar a .NET 4, y simplemente agregar el atributo faltante usted mismo, funcionará bien. Solo tiene que tener el nombre correcto y estar en el espacio de nombres correcto. No necesita estar en un ensamblaje específico.
Marc Gravell
196

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

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 un comparador a la función también.

EqualityComparer<T>.Default.Equals

Más ejemplos aquí y aquí.

Consulte también Información de la persona que llama (C # y Visual Basic)

Daniel Little
fuente
12
¡Brillante! ¿Pero por qué es genérico?
abatishchev
@abatishchev Supongo que no tiene que ser así, solo estaba jugando con la idea de que la función también establezca la propiedad. Veré si puedo actualizar mi respuesta y proporcionar la solución completa. Los ejemplos adicionales hacen un buen trabajo mientras tanto.
Daniel Little
3
Fue introducido por C # 5.0. No tiene nada que ver con .net 4.5, ¡pero esta es una gran solución!
J. Lennon
55
@J. Lennon .net 4.5 todavía tiene algo que ver con eso, después de todo el atributo proviene de algún lugar msdn.microsoft.com/en-au/library/…
Daniel Little
@Lavinski cambia su aplicación a, por ejemplo, .NET 3.5 y vea qué funcionará (en vs2012)
J. Lennon
162

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

Simplemente agregue 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 se inspiró en esta URL actualizada de la publicación del blog

Thomas Levesque
fuente
66
Hay al menos un marco que usa este método, ReactiveUI .
AlSki
Muy tarde, esto significaba pasar por la reflexión, lo que significaba un éxito en el rendimiento. Podría ser aceptable, pero configurar una propiedad no es un lugar donde me gustaría que mi aplicación pase muchos ciclos.
Bruno Brant
1
@BrunoBrant ¿Estás seguro de que hay un éxito en el rendimiento? Según la publicación del blog, la reflexión ocurre durante el tiempo de compilación en lugar del tiempo de ejecución (es decir, reflexión estática).
Nathaniel Elkins
66
Creo que toda su OnPropertyChanged <T> es obsoleta con el nombre del operador de C # 6, lo que hace que este monstruo sea un poco más elegante.
Traubenfuchs
55
@Traubenfuchs, en realidad, el atributo CallerMemberName de C # 5 lo hace aún más simple, ya que no necesita pasar nada en absoluto ...
Thomas Levesque
120

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

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

... y en tiempo de compilación inyecta notificaciones de propiedades modificadas.

Tom Gilder
fuente
77
Creo que esto es exactamente lo que OP estaba buscando cuando preguntaron "¿Podemos implementar algo como 'notificar' en nuestras propiedades? ¿Hay una solución elegante para implementar INotifyPropertyChanged en su clase"
Ashoat
3
Esta es la única solución elegante realmente, y funciona perfectamente como dijo @CADbloke. Y también era escéptico sobre el tejedor, pero verifiqué / volví a verificar el código de IL y es perfecto, es simple, hace todo lo que necesitas y nada más. También engancha y llama cualquier nombre de método que haya designado en la clase base, ya sea NotifyOnProp ..., OnNotify ... no importa, por lo que funciona bien con cualquier clase base que pueda tener y que implemente INotify ... .
NSGaga-mayoría-inactivo
1
Puede verificar fácilmente lo que está haciendo el tejedor, echar un vistazo a la ventana de salida de compilación, enumera todas las cosas de PropertyChanged que ha tejido. El uso de la extensión VScolorOutput con el patrón regex lo "Fody/.*?:",LogCustom2,Trueresalta en el color "Personalizado 2". Lo hice de color rosa brillante para que sea fácil de encontrar. Simplemente Fody todo, es la mejor manera de hacer cualquier cosa que tenga muchos tipeos repetitivos.
CAD bloke
@mahmoudnezarsarhan no, no lo es, recuerdo que hubo un ligero cambio en la forma en que debe configurarse, pero Fody PropertyChanged sigue vivo y activo.
Larry
65

Creo que la gente debería prestar un poco más de atención al rendimiento; realmente impacta la interfaz de usuario cuando hay muchos objetos a 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; échale un vistazo a la comparación de rendimiento de las implementaciones INotifyPropertyChanged .


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

Peijen
fuente
14
-1: no hay sobrecarga de rendimiento: CallerMemberName se cambia a valores literales en tiempo de compilación. Solo intenta descompilar tu aplicación.
JYL
Aquí está la pregunta y respuesta correspondiente: stackoverflow.com/questions/22580623/…
uli78
1
@JYL, tiene razón en que CallerMemberName no agregó una gran sobrecarga. Debo haber implementado algo mal la última vez que lo intenté. Actualizaré el blog y responderé para reflejar el punto de referencia para la implementación de CallerMemberName y Fody más adelante.
Peijen
1
Si tiene una cuadrícula de más de 10,000 en la interfaz de usuario, entonces probablemente debería combinar enfoques para manejar el rendimiento, como la paginación donde solo muestra 10, 50, 100, 250 visitas por página ...
Austin Rhymer
Austin Rhymer, si tiene datos grandes + 50, use la virtualización de datos sin necesidad de cargar todos los datos, ¡cargará solo los datos visibles en el área visualizada de desplazamiento actual!
Bilal
38

Presento una clase 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 utilizando parámetros de referencia.

  • Sin cuerda mágica
  • 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); }
    }
}
TiMoch
fuente
2
Esta es una buena solución, pero el único inconveniente es que hay un pequeño impacto en el rendimiento que involucra el boxeo / unboxing.
MCattle
1
Sugeriría usar protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)y también verificar if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))en Establecer (para subir y guardar cuando se establece por primera vez en el valor predeterminado)
Miquel
1
@Miquel agregar soporte para valores predeterminados personalizados puede ser útil, sin embargo, debe tener cuidado de solo generar el evento modificado cuando el valor realmente cambió. Establecer una propiedad con el mismo valor que tenía no debería generar eventos. Debo admitir que en la mayoría de los casos es inofensivo, sin embargo, he sido mordido varias veces con propiedades que se han configurado miles de veces al mismo valor con eventos que destruyen la capacidad de respuesta de la interfaz de usuario.
TiMoch
1
@stakx Tengo algunas aplicaciones que se basan en esto para admitir el patrón de recuerdo para deshacer / rehacer o para habilitar el patrón de unidad de trabajo en aplicaciones donde nhibernate no es utilizable
TiMoch
1
Realmente me gusta esta solución en particular: notación corta, sin proxy dinámico, sin intromisión de IL, etc. Sin embargo , puede acortarlo eliminando la necesidad de especificar T cada vez para Get haciendo que Get return dynamic. Lo sé, esto afecta el rendimiento del tiempo de ejecución, pero ahora el código para captadores y establecedores finalmente puede ser siempre el mismo y, en una línea , ¡alabado sea el Señor! PD: debe tener cuidado adicional dentro de su método Get (una vez cuando escribe la clase base) al devolver los valores predeterminados para los tipos de valor como dinámicos. Asegúrese de devolver siempre los valores predeterminados correctos (se puede hacer)
evilkos
15

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 tengo la intención de escribir un atributo Postsharp que inyectará el código en el momento de la 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 deba activar más de OnPropertyChanged (si, por ejemplo, tuviera 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 como esto antes aquí . No es exactamente lo que tenía en mente, pero lo suficientemente cerca como para mostrar que la teoría es buena.

Martin Harris
fuente
66
Una herramienta gratuita llamada Fody parece hacer lo mismo, funcionando como un inyector de código genérico en tiempo de compilación. Se puede descargar en Nuget, al igual que sus paquetes de complementos PropertyChanged y PropertyChanging.
Triynko
11

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

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

  • Crear nuevo proyecto
  • Instale el paquete principal del castillo en el proyecto

Install-Package Castle.Core

  • Instale solo bibliotecas ligeras mvvm

Install-Package MvvmLightLibs

  • Agregue 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;
    }
}
  • Cree su 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:

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

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Disfrutar.

ingrese la descripción de la imagen aquí

¡¡¡Atención!!! Todas las propiedades delimitadas deben decorarse con la palabra clave virtual porque las usa el proxy del castillo para anularlas.

testCoder
fuente
Me interesa saber qué versión de Castle estás usando. Estoy utilizando el método 3.3.0 y CreateClassProxy no tiene esos parámetros: type, interfaces to apply, interceptors.
Resumen AB
No importa, estaba usando el CreateClassProxy<T>método genérico . Muy diferente ... hmmm, preguntándome por qué tan limitado con el método genérico. :(
IAbstract
7

Un enfoque muy parecido 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

HokieMike
fuente
5

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 enlazables similares a las conocidas propiedades de dependencia:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}
DotNetMastermind
fuente
1
El enlace está roto.
Guge
4

Basado en la respuesta de Thomas, que fue adaptada de una respuesta de Marc, he convertido el código modificado de propiedad reflectante 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 el de Thomas, excepto que puede pasar propiedades adicionales para notificar. Esto fue necesario para manejar las 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 impulsando una colección de elementos almacenados en una BindingList expuesta a través de un DataGridView. Me ha eliminado la necesidad de hacer llamadas manuales Refresh () a la grilla.

StuffOfInterest
fuente
4

Permítanme presentarles mi propio enfoque llamado Yappi . Pertenece a los generadores de clases derivados del proxy de tiempo de ejecución, que agregan nuevas funciones a un objeto o tipo existente, como el proxy dinámico de Caste Project.

Permite implementar INotifyPropertyChanged una vez en la clase base, y luego declarar clases derivadas en el siguiente estilo, aún admitiendo INotifyPropertyChanged para 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 de clase o proxy derivada se puede ocultar detrás de la siguiente línea:

var animal = Concept.Create<Animal>.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<TConcept> where TConcept:Concept
    {
        //Construct derived Type calling PropertyProxy.ConstructType
        public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
        //Create constructing delegate calling Constructor.Compile
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        var caller = PropertyChanged;
        if(caller!=null)
        {
            caller(this, eventArgs);
        }
    }

    //define implementation
    public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
    {
        public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
        }
        /// <summary>
        /// Overriding property setter implementation.
        /// </summary>
        /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
        /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
        /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
        /// <typeparam name="TResult">Type of property.</typeparam>
        /// <param name="property">PropertyInfo of property.</param>
        /// <returns>Delegate, corresponding to property setter implementation.</returns>
        public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(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<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
            Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);

            var comparer = EqualityComparer<TResult>.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 reflejos después de la construcción tipo y es lo suficientemente rápido.

Kelqualyn
fuente
¿Por qué necesita el TDeclarationparámetro type PropertyImplementation? ¿Seguramente puede encontrar el tipo apropiado para llamar (no callvirt) al getter / setter solo con TImplementation?
Andrew Savinykh
TImplementation funciona en la mayoría de los casos. Las excepciones son: 1. Propiedades redefinidas con "nuevo" C # keyvord. 2. Propiedades de la implementación explícita de la interfaz.
Kelqualyn
3

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 y úselo mientras usa el fragmento 'fullprop'.

la ubicación se puede encontrar en el menú 'Herramientas \ Administrador de fragmentos de código ...' 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 usar las soluciones anteriores)

Ofir
fuente
2

Si está utilizando dinámicas en .NET 4.5, no necesita preocuparse INotifyPropertyChanged.

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

si Name está sujeto a algún control, simplemente funciona bien.

Dilshod
fuente
1
¿Alguna desventaja de usar esto?
juFo
2

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); }
    }
}
Ofir
fuente
2
¿Eso es rápido? ¿El acceso al marco de la pila no está sujeto a algún requisito de permiso? ¿Es eso robusto en un contexto de uso asíncrono / espera?
Stéphane Gourichon
@ StéphaneGourichon No, no lo es. Acceder al marco de la pila significa un éxito considerable en la mayoría de los casos.
Bruno Brant
Tenga en cuenta que la alineación puede ocultar el get_Foométodo en modo Release.
bytecode77
2

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 = "", ...a...,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);
        }
    }
}
giammin
fuente
2

Resolví de esta manera (es un poco laborioso, pero seguramente es el 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

Esto transforma 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 simplemente 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}

Lanzo para reemplazar el código IL del método establecido, pero no puedo escribir mucho código compilado en IL ... ¡Si algún día lo escribo, te lo diré!

Lucio Menci
fuente
2

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));
    }
}
Mike Ward
fuente
2

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

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 Reorganizador, si crea un fragmento de patrón / búsqueda, también puede automatizar su flujo de trabajo al convertir simples campos de apoyo 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); }
}
Scott Barnes
fuente
2

He escrito un artículo que ayuda con esto ( https://msdn.microsoft.com/magazine/mt736453 ). Puede usar el paquete SolSoft.DataBinding NuGet. Entonces puedes escribir código como este:

public class TestViewModel : IRaisePropertyChanged
{
  public TestViewModel()
  {
    this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
  }

  private readonly NotifyProperty<string> m_nameProperty;
  public string Name
  {
    get
    {
      return m_nameProperty.Value;
    }
    set
    {
      m_nameProperty.SetValue(value);
    }
  }

  // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}

Beneficios:

  1. la clase base es opcional
  2. sin reflexión sobre cada "valor establecido"
  3. pueden tener propiedades que dependen de otras propiedades, y todas ellas generan automáticamente los eventos apropiados (el artículo tiene un ejemplo de esto)
Mark Sowul
fuente
2

Aunque obviamente hay muchas maneras de hacer esto, con la excepción de las respuestas mágicas de AOP, ninguna de las respuestas parece enfocarse en establecer la propiedad de un Modelo directamente desde el modelo de vista sin tener un campo local para referencia.

El problema es que no puede hacer referencia a una propiedad. Sin embargo, puede usar una Acción para establecer esa propiedad.

protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
    {
        return false;
    }

    property(newValue);
    RaisePropertyChanged(propertyName);
    return true;
}

Esto se puede usar como el siguiente extracto de código.

public int Prop {
    get => model.Prop;
    set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}

Consulte este repositorio de BitBucket para obtener una implementación completa del método y algunas formas diferentes de lograr el mismo resultado, incluido un método que usa LINQ y un método que usa reflexión. Tenga en cuenta que estos métodos son más lentos en cuanto al rendimiento.

Dan
fuente
1

Otras cosas que quizás desee considerar al implementar este tipo de propiedades es el hecho de que INotifyPropertyChang * ed * ing usa clases de argumentos de eventos.

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

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

Blog de Josh Smiths

Peter
fuente
1

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

Para citar de su sitio web ...


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

En cambio, escriba propiedades como esta:

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

Tenga en cuenta que no es necesario incluir el nombre de la propiedad como una cadena. ActiveSharp 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).

Ian Ringrose
fuente
1

Una idea usando 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
Jack
fuente
Esto es genial, me gusta más que el enfoque de expresión. En el lado negativo, debería ser más lento.
nawfal
1

Me doy cuenta de que esta pregunta ya tiene muchísimas respuestas, pero ninguna de ellas me pareció correcta. Mi problema es que no quiero ningún éxito en el rendimiento y estoy dispuesto a soportar un poco de verbosidad solo por esa razón. Tampoco me importan demasiado las propiedades automotrices, 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 le 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 el valor anterior! = Valor nuevo
  • Notificar múltiples propiedades a la vez

Contras

  • Sin propiedades automáticas (¡sin embargo, puede agregar soporte para ambos!)
  • Cierta verbosidad
  • Boxeo (¿pequeño golpe de rendimiento?)

Por desgracia, todavía es mejor que hacer esto,

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

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

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

James M
fuente
1

Se me ocurrió esta clase base para implementar el patrón observable, más o menos hace lo que necesita ( "automáticamente" implementando el conjunto y obtener). 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); }
    }
  }
Homero Barbosa
fuente
1

Sugiero usar ReactiveProperty. Este es el método más corto, excepto Fody.

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    ...
    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

en lugar

public class Data
{
    // Don't need boiler-plate and INotifyPropertyChanged

    // props
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
}

( DOCS )

soi
fuente
0

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));
    }
}
Toro
fuente
0

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

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. sin refelction
  2. notación corta
  3. sin cadena mágica en su código comercial
  4. Reutilización de PropertyChangedEventArgs en la aplicación
  5. Posibilidad de notificar múltiples propiedades en una sola declaración
Bruno
fuente
0

Utilizar este

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>(T target)
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Target should be an interface", "target");

        var proxy = new Observable<T>(target);
        return (T)proxy.GetTransparentProxy();
    }
}

internal class Observable<T> : 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;
    }
}

}

Amigo505
fuente