Nombre de la propiedad INotifyPropertyChanged – hardcode vs reflection?

¿Cuál es la mejor manera de especificar un nombre de propiedad cuando se utiliza INotifyPropertyChanged?

La mayoría de los ejemplos codifican el nombre de la propiedad como un argumento en el evento PropertyChanged. Estaba pensando en usar MethodBase.GetCurrentMethod.Name.Substring (4) pero estoy un poco incómodo con la reflexión de arriba.

No olvide una cosa: el evento PropertyChanged es consumido principalmente por componentes que usarán la reflexión para obtener el valor de la propiedad nombrada.

El ejemplo más obvio es el enlace de datos.

Cuando PropertyChanged evento PropertyChanged , pasando el nombre de la propiedad como parámetro, debe saber que es probable que el suscriptor de este evento use la reflexión al llamar, por ejemplo, GetProperty (al menos la primera vez si usa un caché de PropertyInfo ), luego GetValue . Esta última llamada es una invocación dinámica (MethodInfo.Invoke) del método getter de la propiedad, que cuesta más que GetProperty que solo consulta metadatos. (Tenga en cuenta que el enlace de datos se basa en todo el elemento TypeDescriptor , pero la implementación predeterminada utiliza la reflexión).

Así que, por supuesto, usar nombres de propiedades de código duro cuando active PropertyChanged es más eficiente que utilizar la reflexión para obtener dinámicamente el nombre de la propiedad, pero en mi humilde opinión, es importante equilibrar sus pensamientos. En algunos casos, la sobrecarga del rendimiento no es tan crítica, y podría beneficiarse de algún tipo en el mecanismo de activación de eventos fuertemente tipados.

Esto es lo que uso a veces en C # 3.0, cuando las actuaciones no serían una preocupación:

 public class Person : INotifyPropertyChanged { private string name; public string Name { get { return this.name; } set { this.name = value; FirePropertyChanged(p => p.Name); } } private void FirePropertyChanged(Expression> propertySelector) { if (PropertyChanged == null) return; var memberExpression = propertySelector.Body as MemberExpression; if (memberExpression == null) return; PropertyChanged(this, new PropertyChangedEventArgs(memberExpression.Member.Name)); } public event PropertyChangedEventHandler PropertyChanged; } 

Observe el uso del árbol de expresiones para obtener el nombre de la propiedad y el uso de la expresión lambda como Expression :

 FirePropertyChanged(p => p.Name); 

La reflexión aquí es bastante exagerada, especialmente desde INotifyPropertyChanged se llama mucho . Lo mejor es codificar el valor si puedes.

Si no está preocupado por el rendimiento, entonces analizaré los diversos enfoques mencionados a continuación y elegiré los que requieran la menor cantidad de encoding. Si pudieras hacer algo para eliminar por completo la necesidad de una llamada explícita, entonces sería mejor (por ejemplo, AOP).

En .NET 4.5 (C # 5.0) hay un nuevo atributo llamado – CallerMemberName que ayuda a evitar los nombres de las propiedades codificadas impidiendo la aparición de errores si los desarrolladores deciden cambiar el nombre de una propiedad, aquí hay un ejemplo:

 public event PropertyChangedEventHandler PropertyChanged = delegate { }; public void OnPropertyChanged([CallerMemberName]string propertyName="") { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private string name; public string Name { get { return name; } set { name = value; OnPropertyChanged(); } } 

El golpe de rendimiento involucrado en el uso de árboles de expresión se debe a la resolución repetida del árbol de expresiones.

El siguiente código aún usa árboles de expresión y tiene las ventajas consiguientes de ser refactorizado amigable y amigable con la ofuscación, pero en realidad es aproximadamente 40% más rápido (pruebas muy duras) que la técnica habitual, que consiste en actualizar un objeto PropertyChangedEventArgs para cada notificación de cambio .

Es más rápido y evita el golpe de rendimiento del árbol de expresiones porque almacenamos en caché un objeto estático PropertyChangedEventArgs para cada propiedad.

Hay una cosa que todavía no estoy haciendo: tengo la intención de agregar algún código que compruebe las comstackciones de depuración de que el nombre de propiedad para el objeto provisto PropertChangedEventArgs coincide con la propiedad dentro de la cual se está utilizando. En este momento, con este código todavía está posible para el desarrollador para suministrar el objeto equivocado.

Echale un vistazo:

  public class Observable : INotifyPropertyChanged where T : Observable { public event PropertyChangedEventHandler PropertyChanged; protected static PropertyChangedEventArgs CreateArgs( Expression> propertyExpression) { var lambda = propertyExpression as LambdaExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else { memberExpression = lambda.Body as MemberExpression; } var propertyInfo = memberExpression.Member as PropertyInfo; return new PropertyChangedEventArgs(propertyInfo.Name); } protected void NotifyChange(PropertyChangedEventArgs args) { if (PropertyChanged != null) { PropertyChanged(this, args); } } } public class Person : Observable { // property change event arg objects static PropertyChangedEventArgs _firstNameChangeArgs = CreateArgs(x => x.FirstName); static PropertyChangedEventArgs _lastNameChangeArgs = CreateArgs(x => x.LastName); string _firstName; string _lastName; public string FirstName { get { return _firstName; } set { _firstName = value; NotifyChange(_firstNameChangeArgs); } } public string LastName { get { return _lastName; } set { _lastName = value; NotifyChange(_lastNameChangeArgs); } } } 

Romano:

Yo diría que ni siquiera necesitaría el parámetro “Persona”; en consecuencia, un fragmento completamente genérico como el siguiente debería hacer:

 private int age; public int Age { get { return age; } set { age = value; OnPropertyChanged(() => Age); } } private void OnPropertyChanged(Expression> exp) { //the cast will always succeed MemberExpression memberExpression = (MemberExpression) exp.Body; string propertyName = memberExpression.Member.Name; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } 

… sin embargo, prefiero apegar a los parámetros de cadena con validación condicional en comstackciones de depuración. Josh Smith publicó una buena muestra sobre esto:

Una clase base que implementa INotifyPropertyChanged

Saludos 🙂 Philipp

Sí, veo el uso y la simplicidad de la función que estás sugiriendo, pero al considerar el costo de ejecución debido a la reflexión, sí, es una mala idea. Lo que uso para este escenario es tener un fragmento de código agregado correctamente para aprovechar el tiempo y error al escribir una propiedad con toda la activación del evento Notifyproperty.

Otro método MUY AGRADABLE que puedo pensar es

Implementación automática de INotifyPropertyChanged with Aspects
AOP: progtwigción orientada a aspectos

Buen artículo sobre codeproject: Implementación de AOP de INotifyPropertyChanged

Es posible que se interesó en esta discusión sobre

“Mejores prácticas: cómo implementar INotifyPropertyChanged, ¿verdad?”

también.

Sin ser irreverente, entre Hardcode y reflexión, mi elección es: notifypropertyweaver .

Este paquete de Visual Studio le permite obtener los beneficios de la reflexión (mantenibilidad, legibilidad, …) sin tener que perder perfs.

En realidad, solo tiene que implementar INotifyPropertyChanged y agrega todas las “cosas de notificación” en la comstackción.

Esto también es completamente parametrable si quiere optimizar completamente su código.

Por ejemplo, con notifypropertyweaver, tendrá este código en su editor:

 public class Person : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public string GivenNames { get; set; } public string FamilyName { get; set; } public string FullName { get { return string.Format("{0} {1}", GivenNames, FamilyName); } } } 

En lugar de :

 public class Person : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string givenNames; public string GivenNames { get { return givenNames; } set { if (value != givenNames) { givenNames = value; OnPropertyChanged("GivenNames"); OnPropertyChanged("FullName"); } } } private string familyName; public string FamilyName { get { return familyName; } set { if (value != familyName) { familyName = value; OnPropertyChanged("FamilyName"); OnPropertyChanged("FullName"); } } } public string FullName { get { return string.Format("{0} {1}", GivenNames, FamilyName); } } public virtual void OnPropertyChanged(string propertyName) { var propertyChanged = PropertyChanged; if (propertyChanged != null) { propertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } 

Para hablantes de francés: Améliorez la lisibilité de votre code et simplifiez vous la vie avec notifypropertyweaver

Además, encontramos un problema donde obtener un nombre de método funcionaba de manera diferente en las comstackciones de depuración frente a versión:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/244d3f24-4cc4-4925-aebe-85f55b39ec92

(El código que estábamos usando no era exactamente reflection en la forma en que usted sugirió, pero nos convenció de que codificar el nombre de la propiedad era la solución más rápida y confiable).

El problema con el método basado en la reflexión es que es bastante caro y no es terriblemente rápido. Claro, es mucho más flexible y menos frágil para la refactorización.

Sin embargo, realmente puede perjudicar el rendimiento, especialmente cuando las cosas se llaman con frecuencia. El método de stackframe, también (creo) tiene problemas en CAS (por ejemplo, niveles de confianza restringida, como XBAP). Lo mejor es codificarlo con fuerza.

Si busca una notificación de propiedad rápida y flexible en WPF, hay una solución: use DependencyObject 🙂 para lo que fue diseñado. Si no desea tomar la dependencia, o preocuparse por los problemas de afinidad de la secuencia, mueva el nombre de la propiedad a una constante y ¡auge! tu bien

Es posible que desee evitar INotifyPropertyChanged por completo. Agrega un código de contabilidad innecesario a su proyecto. Considere usar Update Controls .NET en su lugar.

Hice algo como esto una vez como experimento, desde la memoria funcionó bien, y eliminé la necesidad de codificar todos los nombres de propiedad en cadenas. El rendimiento podría estar en juego si construyes una aplicación de servidor de gran volumen, en el escritorio probablemente nunca notarás la diferencia.

 protected void OnPropertyChanged() { OnPropertyChanged(PropertyName); } protected string PropertyName { get { MethodBase mb = new StackFrame(1).GetMethod(); string name = mb.Name; if(mb.Name.IndexOf("get_") > -1) name = mb.Name.Replace("get_", ""); if(mb.Name.IndexOf("set_") > -1) name = mb.Name.Replace("set_", ""); return name; } } 

Como C # 6.0 hay una palabra clave nameof () , se evaluará en tiempo de comstackción, por lo que tendrá el rendimiento como valor codificado y está protegida contra la discrepancia con la propiedad notificada.

 public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string info) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info)); } public string SelectedItem { get { return _selectedItem; } set { if (_selectedItem != value) { _selectedItem = value; NotifyPropertyChanged(nameof(SelectedItem)); } } } private string _selectedItem;