property event ejemplo change wpf binding .net-3.5 inotifypropertychanged

wpf - event - Implementando NotifyPropertyChanged sin cadenas mágicas



wpf inotifypropertychanged (5)

En realidad, hablamos de esto también para nuestros proyectos y hablamos mucho sobre los pros y los contras. Al final, decidimos mantener el método regular pero usamos un campo para él.

public class MyModel { public const string ValueProperty = "Value"; public int Value { get{return mValue;} set{mValue = value; RaisePropertyChanged(ValueProperty); } }

Esto ayuda cuando se refactoriza, mantiene nuestro rendimiento y es especialmente útil cuando utilizamos PropertyChangedEventManager , donde necesitaríamos nuevamente las cadenas codificadas.

public bool ReceiveWeakEvent(Type managerType, object sender, System.EventArgs e) { if(managerType == typeof(PropertyChangedEventManager)) { var args = e as PropertyChangedEventArgs; if(sender == model) { if (args.PropertyName == MyModel.ValueProperty) { } return true; } } }

Posible duplicado:
typesafe NotifyPropertyChanged utilizando linq expressions

Estoy trabajando en una aplicación de equipo grande que sufre un uso intensivo de cadenas mágicas en forma de NotifyPropertyChanged("PropertyName") , la implementación estándar cuando se consulta a Microsoft. También estamos sufriendo de un gran número de propiedades con nombres erróneos (que trabajan con un modelo de objetos para un módulo de cálculo que tiene cientos de propiedades calculadas almacenadas), todos los cuales están vinculados a la IU.

Mi equipo experimenta muchos errores relacionados con los cambios en el nombre de la propiedad que conducen a cadenas mágicas incorrectas y enlaces de ruptura. Deseo resolver el problema implementando notificaciones modificadas de propiedad sin usar cadenas mágicas. Las únicas soluciones que he encontrado para .Net 3.5 incluyen expresiones lambda. (por ejemplo: Implementando INotifyPropertyChanged - ¿existe una mejor manera? )

Mi gerente está extremadamente preocupado por el costo de rendimiento de cambiar de

set { ... OnPropertyChanged("PropertyName"); }

a

set { ... OnPropertyChanged(() => PropertyName); }

de donde se extrae el nombre

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression) { MemberExpression body = selectorExpression.Body as MemberExpression; if (body == null) throw new ArgumentException("The body must be a member expression"); OnPropertyChanged(body.Member.Name); }

Considere una aplicación como una hoja de cálculo, donde cuando un parámetro cambia, aproximadamente un centenar de valores se recalculan y actualizan en la interfaz de usuario en tiempo real. ¿Este cambio es tan costoso que afectará la capacidad de respuesta de la IU? Ni siquiera puedo justificar la prueba de este cambio en este momento, ya que tomaría aproximadamente 2 días actualizar a los instaladores de propiedades en varios proyectos y clases.


Personalmente, me gusta usar NotificationObject Microsoft PRISM por este motivo, y supongo que su código está razonablemente optimizado ya que fue creado por Microsoft.

Me permite usar código como RaisePropertyChanged(() => this.Value); , además de mantener las "Cadenas Mágicas" para que no rompa ningún código existente.

Si miro su código con Reflector, su implementación puede ser recreada con el siguiente código

public class ViewModelBase : INotifyPropertyChanged { // Fields private PropertyChangedEventHandler propertyChanged; // Events public event PropertyChangedEventHandler PropertyChanged { add { PropertyChangedEventHandler handler2; PropertyChangedEventHandler propertyChanged = this.propertyChanged; do { handler2 = propertyChanged; PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler)Delegate.Combine(handler2, value); propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2); } while (propertyChanged != handler2); } remove { PropertyChangedEventHandler handler2; PropertyChangedEventHandler propertyChanged = this.propertyChanged; do { handler2 = propertyChanged; PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler)Delegate.Remove(handler2, value); propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2); } while (propertyChanged != handler2); } } protected void RaisePropertyChanged(params string[] propertyNames) { if (propertyNames == null) { throw new ArgumentNullException("propertyNames"); } foreach (string str in propertyNames) { this.RaisePropertyChanged(str); } } protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression) { string propertyName = PropertySupport.ExtractPropertyName<T>(propertyExpression); this.RaisePropertyChanged(propertyName); } protected virtual void RaisePropertyChanged(string propertyName) { PropertyChangedEventHandler propertyChanged = this.propertyChanged; if (propertyChanged != null) { propertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } public static class PropertySupport { // Methods public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } MemberExpression body = propertyExpression.Body as MemberExpression; if (body == null) { throw new ArgumentException("propertyExpression"); } PropertyInfo member = body.Member as PropertyInfo; if (member == null) { throw new ArgumentException("propertyExpression"); } if (member.GetGetMethod(true).IsStatic) { throw new ArgumentException("propertyExpression"); } return body.Member.Name; } }


Si le preocupa que la solución lambda-expression-tree sea demasiado lenta, perfile y descubra. Sospecho que el tiempo dedicado a abrir el árbol de expresiones sería bastante menor que la cantidad de tiempo que la interfaz de usuario pasará refrescante en respuesta.

Si encuentra que es demasiado lento y necesita usar cadenas literales para cumplir con sus criterios de rendimiento, este es un enfoque que he visto:

Cree una clase base que implemente INotifyPropertyChanged y RaisePropertyChanged un método RaisePropertyChanged . Ese método verifica si el evento es nulo, crea PropertyChangedEventArgs y dispara el evento, todo lo habitual.

Pero el método también contiene algunos diagnósticos adicionales: realiza algunas Reflexiones para asegurarse de que la clase realmente tenga una propiedad con ese nombre. Si la propiedad no existe, arroja una excepción. Si la propiedad existe, memoriza ese resultado (por ejemplo, agregando el nombre de la propiedad a una HashSet<string> estática HashSet<string> ), por lo que no tiene que volver a hacer la comprobación de Reflexión.

Y listo: sus pruebas automatizadas comenzarán a fallar tan pronto como renombre una propiedad pero no actualice la cadena mágica. (Supongo que tiene pruebas automáticas para sus ViewModels, ya que esa es la razón principal para usar MVVM).

Si no quiere fallar tan ruidosamente en la producción, puede poner el código de diagnóstico adicional dentro de #if DEBUG .


Hice una prueba exhaustiva de NotifyPropertyChanged para establecer el impacto de cambiar a las expresiones lambda.

Aquí estaban los resultados de mi prueba:

Como puede ver, el uso de la expresión lambda es aproximadamente 5 veces más lento que la implementación de cambio de propiedad de cadena simple codificada, pero los usuarios no deben preocuparse, porque incluso así es capaz de extraer cien mil cambios de propiedad por segundo en mi tan especial computadora de trabajo. Como tal, el beneficio obtenido al dejar de tener que codificar cadenas de caracteres y ser capaz de tener instaladores de una sola línea que se ocupan de todo su negocio supera con creces el costo del rendimiento para mí.

La Prueba 1 usó la implementación del patrón estándar, con una verificación para ver que la propiedad realmente había cambiado:

public UInt64 TestValue1 { get { return testValue1; } set { if (value != testValue1) { testValue1 = value; InvokePropertyChanged("TestValue1"); } } }

La Prueba 2 fue muy similar, con la adición de una función que permite al evento rastrear el valor anterior y el nuevo valor. Debido a que estas características iban a estar implícitas en mi nuevo método de configuración básica, quería ver qué parte de la nueva sobrecarga se debía a esa característica:

public UInt64 TestValue2 { get { return testValue2; } set { if (value != testValue2) { UInt64 temp = testValue2; testValue2 = value; InvokePropertyChanged("TestValue2", temp, testValue2); } } }

El Test 3 fue donde el caucho se encontró con el camino, y puedo mostrar esta nueva y hermosa sintaxis para realizar todas las acciones de propiedad observables en una sola línea:

public UInt64 TestValue3 { get { return testValue3; } set { SetNotifyingProperty(() => TestValue3, ref testValue3, value); } }

Implementación

En mi clase BindingObjectBase, que todos los ViewModels terminan heredando, se encuentra la implementación que impulsa la nueva característica. He eliminado el manejo de errores para que la función sea clara:

protected void SetNotifyingProperty<T>(Expression<Func<T>> expression, ref T field, T value) { if (field == null || !field.Equals(value)) { T oldValue = field; field = value; OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(GetPropertyName(expression), oldValue, value)); } } protected string GetPropertyName<T>(Expression<Func<T>> expression) { MemberExpression memberExpression = (MemberExpression)expression.Body; return memberExpression.Member.Name; }

Los tres métodos se encuentran en la rutina OnPropertyChanged, que sigue siendo el estándar:

public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(sender, e); }

Prima

Si alguien tiene curiosidad, el PropertyChangedExtendedEventArgs es algo que simplemente se me ocurrió para extender el PropertyChangedEventArgs estándar, por lo que una instancia de la extensión siempre puede estar en lugar de la base. Aprovecha el conocimiento del valor anterior cuando se cambia una propiedad usando SetNotifyingProperty, y pone esta información a disposición del controlador.

public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs { public virtual T OldValue { get; private set; } public virtual T NewValue { get; private set; } public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue) : base(propertyName) { OldValue = oldValue; NewValue = newValue; } }


Una solución simple es simplemente preprocesar todos los archivos antes de la compilación, detectar las llamadas OnPropertyChanged que se definen en los bloques set {...}, determinar el nombre de la propiedad y corregir el parámetro del nombre en consecuencia.

Puede hacer esto usando una herramienta ad-hoc (esa sería mi recomendación), o usar un analizador C # (o VB.NET) real (como los que se pueden encontrar aquí: Analizador para C # ).

Creo que es una forma razonable de hacerlo. Por supuesto, no es muy elegante ni inteligente, pero tiene un impacto cero en el tiempo de ejecución y sigue las reglas de Microsoft.

Si desea guardar un poco de tiempo de compilación, puede tener ambas formas usando directivas de compilación, como esta:

set { #if DEBUG // smart and fast compile way OnPropertyChanged(() => PropertyName); #else // dumb but efficient way OnPropertyChanged("MyProp"); // this will be fixed by buid process #endif }