2008-09-26 20 views
41

¿Cuál es la mejor manera de especificar un nombre de propiedad cuando se utiliza INotifyPropertyChanged?Nombre de la propiedad INotifyPropertyChanged - hardcode vs reflection?

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.

+10

.NET 4.5 ofrece una buena solución para esto con el atributo [CallerMemberName]. Vea un ejemplo de su uso en la documentación de MSDN http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx –

Respuesta

44

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

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

Cuando dispares PropertyChanged evento, pasando el nombre de la propiedad como un parámetro, usted debe saber que el suscriptor de este evento es probable que utilizar la reflexión llamando, 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 el GetProperty que solo consulta metadatos. (Tenga en cuenta que el enlace de datos se basa en todo el asunto TypeDescriptor, pero la implementación predeterminada usa la reflexión.)

Así que, por supuesto, utilizar nombres de propiedades de código duro al activar PropertyChanged es más eficiente que utilizar la reflexión para obtener dinámicamente el nombre del 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ía 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<TValue>(Expression<Func<Person, TValue>> 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; 
} 

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

FirePropertyChanged(p => p.Name); 
+1

Esto parece excesivo para un problema de cambio de propiedad.Si bien mantiene todo en código refactorizable, terminas con mucho más código para mantener y ese código es menos legible que el concepto original de OnPropertyChanged ("Nombre") o un método similar. –

+0

+1 para explicar y confirmar que "es probable que el suscriptor de este evento use la reflexión llamando, por ejemplo, GetProperty". Me preguntaba qué pasó después de que el objeto de datos hiciera OnPropertyChanged ("Nombre"). – Lernkurve

+2

Muy bueno. ¿Hay alguna forma de evitar usar cadenas completamente? – strongriley

19

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

Si no está preocupado por el rendimiento, me gustaría ver los diversos enfoques mencionados a continuación y elegir los que requieren la menor cantidad de codificación. Si pudiera hacer algo para eliminar por completo la necesidad de una llamada explícita, entonces sería lo mejor (por ejemplo, AOP).

6

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

0

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; 
    } 
} 
+0

Me gusta la forma creativa de hacerlo. Probablemente no debería usarse en la práctica. Pero el concepto es interesante. – TamusJRoyce

1

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 :). Es para lo que fue diseñada. 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

11

romana:

diría que ni siquiera necesita el parámetro "Persona" - en consecuencia, una forma completamente genérico fragmento de código como el siguiente debe hacer:

private int age; 
public int Age 
{ 
    get { return age; } 
    set 
    { 
    age = value; 
    OnPropertyChanged(() => Age); 
    } 
} 


private void OnPropertyChanged<T>(Expression<Func<T>> 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 que se pega a los parámetros de cadena con la validación condicional en versiones de depuración. Josh Smith anotó una muestra agradable en esto:

A base class which implements INotifyPropertyChanged

Saludos :) Philipp

+0

Estoy votando por las cadenas con validación condicional. Es simple y funciona. Al final del día, ninguna de estas soluciones de reflexión resuelve el problema que me gustaría resolver: compilar el control del tiempo de las notificaciones de cambio de propiedad. –

1

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

4

Otro método muy bueno que puedo pensar es

Auto-implemento INotifyPropertyChanged con aspectos
AOP: orientada a aspectos de programación

bonito artículo sobre CodeProject: AOP Implementation of INotifyPropertyChanged

+1

También creo que AOP es la mejor solución para ese tipo de problemas. Pero sabes que la mayoría de los desarrolladores de C# realmente no saben qué es AOP. –

14

El impacto en el rendimiento implicado en el uso de árboles de expresión se debe a la resolución repetida del árbol de expresión.El siguiente código aún usa árboles de expresiones y tiene las consiguientes ventajas de ser amigable y amigable con la refactorizació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. por 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 un código que comprueba las compilaciones de depuración de que el nombre de propiedad para el objeto PropertChangedEventArgs proporcionado coincide con la propiedad en la que se está utilizando, en este momento con este código aún es posible que el desarrollador suministre el objeto equivocado.

Hay que ver:

public class Observable<T> : INotifyPropertyChanged 
    where T : Observable<T> 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected static PropertyChangedEventArgs CreateArgs(
     Expression<Func<T, object>> 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<Person> 
{ 
    // 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); 
     } 
    } 
} 
+0

Nota, en realidad hay un problema con el código anterior: limita T al subtipo inmediato (ya que el subtipo inmediato especifica T como sí mismo). Esto significa que no puede usar CreateArgs cuando se trata de propiedades en una clase que está más abajo en la jerarquía de Person. Una solución simple para esto es hacer que el método CreateArgs sea genérico, es decir, CreateArgs en lugar de tener la clase Observable genérica. Es un poco más difícil de usar ya que debe especificar explícitamente el tipo, por ejemplo: staticChangedEventArgs static _firstNameChangeArgs = CreateArgs (x => x.FirstName); – Phil

+0

Cualquier cosa que me obligue a cambiar mi clase base es un gran no-no. No tengo exactamente la opción de cambiar la clase base por algo que ya tiene una clase base diferente o tiene una clase base autogenerada. –

+1

Orion ... si ese es el caso, debería ser lo suficientemente fácil convertir lo anterior de una clase base Observable a una clase auxiliar (por ejemplo, 'ObservableHelper') a la que puede llamar explícitamente. Si está interesado, probablemente podría publicar una actualización que cubriría el caso de uso en el que no puede controlar qué clase de base se utiliza o no desea introducir una. Sin embargo, donde controlas la clase base, creo que hay ventajas en el uso de una clase base; la simplicidad de uso es la principal. – Phil

2

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

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

En realidad, solo tiene que implementar INotifyPropertyChanged y agrega todo el "material de notificación" en la compilación.

Esto también es totalmente parametrable si desea optimizar al máximo su código.

Por ejemplo, con notifypropertyweaver, tendrá este código en que el 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 los francófonos: Améliorez la lisibilité de votre code et simplifiez vous la vie avec notifypropertyweaver

14

En .NET 4. 5 (C# 5.0) hay un nuevo atributo llamado - CallerMemberName ayuda a evitar los nombres de propiedades codificadas prevenir la aparición de errores si los desarrolladores deciden cambiar un nombre de propiedad, aquí está 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(); 
    } 
} 
0

Puesto que C# 6.0, hay una nameof() palabra clave se evaluará en tiempo de compilación, por lo que tendrá el rendimiento como valor codificado y está protegido 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; 
Cuestiones relacionadas