Nom de la propriété INotifyPropertyChanged – hardcode vs reflection?

Quelle est la meilleure façon de spécifier un nom de propriété lors de l’utilisation d’INotifyPropertyChanged?

La plupart des exemples codent en dur le nom de la propriété en tant qu’argument sur l’événement PropertyChanged. Je pensais à utiliser MethodBase.GetCurrentMethod.Name.Subssortingng (4) mais je suis un peu inquiet au sujet de la surcharge de reflection.

N’oubliez pas une chose: PropertyChanged événement de PropertyChanged est principalement utilisé par les composants qui utiliseront la reflection pour obtenir la valeur de la propriété nommée.

L’exemple le plus évident est la liaison de données.

Lorsque vous déclenchez un événement PropertyChanged transmettant le nom de la propriété en tant que paramètre, vous devez savoir que l’abonné de cet événement utilisera probablement la reflection en appelant, par exemple, GetProperty (au moins la première fois s’il utilise un cache de PropertyInfo ), puis GetValue . Ce dernier appel est un appel dynamic (MethodInfo.Invoke) de la méthode getter de propriété, qui coûte plus cher que GetProperty qui interroge uniquement les métadonnées. (Notez que la liaison de données repose sur la totalité de l’object TypeDescriptor – mais l’implémentation par défaut utilise la reflection.)

Ainsi, bien sûr, l’utilisation de noms de propriété de code dur lors du déclenchement de PropertyChanged est plus efficace que d’utiliser la reflection pour obtenir dynamicment le nom de la propriété, mais à mon humble avis, il est important d’équilibrer vos pensées. Dans certains cas, la surcharge de performances n’est pas si critique, et vous pourriez bénéficier d’un mécanisme de déclenchement d’événement fortement typé.

Voici ce que j’utilise parfois en C # 3.0, lorsque les performances ne seraient pas un problème:

 public class Person : INotifyPropertyChanged { private ssortingng name; public ssortingng 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; } 

Notez l’utilisation de l’arborescence d’expression pour obtenir le nom de la propriété et l’utilisation de l’expression lambda en tant qu’expression:

 FirePropertyChanged(p => p.Name); 

La surcharge de reflection ici est à peu près exagérée, surtout depuis que INotifyPropertyChanged est appelé beaucoup . Il est préférable de coder la valeur si vous le pouvez.

Si vous n’êtes pas préoccupé par la performance, je regarderais les différentes approches mentionnées ci-dessous et choisirais celles qui nécessitent le moins de codage. Si vous pouviez faire quelque chose pour supprimer complètement le besoin d’un appel explicite, cela serait préférable (par exemple, AOP).

Dans .NET 4.5 (C # 5.0), il existe un nouvel atsortingbut appelé – CallerMemberName: il évite les noms de propriété codés en dur empêchant l’apparition de bogues si les développeurs décident de modifier un nom de propriété, voici un exemple:

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

L’attaque de performance impliquée dans l’utilisation d’arbres d’expression est due à la résolution répétée de l’arbre d’expression.

Le code suivant utilise toujours des arbres d’expression et présente donc les avantages d’être refait à neuf et convivial, mais est en réalité environ 40% plus rapide (tests très approximatifs) que la technique habituelle – qui consiste à modifier un object PropertyChangedEventArgs pour chaque notification de changement. .

Il est plus rapide et évite l’impact de l’arborescence d’expression sur les performances, car nous mettons en cache un object PropertyChangedEventArgs statique pour chaque propriété.

Il y a une chose que je ne fais pas encore – j’ai l’intention d’append du code qui vérifie les versions de débogage que le nom de propriété de l’object PropertChangedEventArgs fourni correspond à la propriété dans laquelle il est utilisé – pour le moment possible pour le développeur de fournir le mauvais object.

Vérifiez-le:

  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); ssortingng _firstName; ssortingng _lastName; public ssortingng FirstName { get { return _firstName; } set { _firstName = value; NotifyChange(_firstNameChangeArgs); } } public ssortingng LastName { get { return _lastName; } set { _lastName = value; NotifyChange(_lastNameChangeArgs); } } } 

Romain:

Je dirais que vous n’auriez même pas besoin du paramètre “Person” – par conséquent, un extrait complètement générique comme celui ci-dessous devrait faire:

 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; ssortingng propertyName = memberExpression.Member.Name; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } 

… Cependant, je préfère coller aux parameters de chaîne avec validation conditionnelle dans les versions Debug. Josh Smith a publié un joli échantillon à ce sujet:

Une classe de base qui implémente INotifyPropertyChanged

Cheers 🙂 Philipp

Oui, je vois l’utilisation et la simplicité de la fonction que vous proposez, mais quand on considère le coût courant dû à la reflection, c’est une mauvaise idée. Ce que j’utilise pour ce scénario, c’est d’append un extrait de code pour profiter du temps et erreur lors de l’écriture d’une propriété avec tous les déclenchements d’événement Notifyproperty.

Une autre méthode TRES NICE à laquelle je peux penser est

Implémenter automatiquement INotifyPropertyChanged avec des aspects
AOP: programmation orientée aspect

Bel article sur codeproject: Implémentation AOP de INotifyPropertyChanged

Vous pourriez être intéressé dans cette discussion sur

“Meilleures pratiques: Comment implémenter INotifyPropertyChanged, n’est-ce pas?”

aussi.

Sans être irrévérencieux, entre Hardcode et reflection, mon choix est: notifypropertyweaver .

Ce package Visual Studio vous permet de bénéficier des avantages de la reflection (maintenabilité, lisibilité, ..) sans avoir à perdre des perfs.

En fait, il vous suffit d’implémenter INotifyPropertyChanged et d’append tous les “éléments de notification” à la compilation.

Ceci est également entièrement paramétrable si vous souhaitez optimiser pleinement votre code.

Par exemple, avec notifypropertyweaver, vous aurez ce code dans votre éditeur:

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

Au lieu de :

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

Pour les francophones: Améliorez la lisibilité de votre code et simplifiez vous la vie avec notifypropertyweaver

De plus, nous avons trouvé un problème où l’obtention d’un nom de méthode fonctionnait différemment dans les versions Debug et Release:

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

(Le code que nous utilisions ne reflétait pas exactement ce que vous aviez suggéré, mais il nous a convaincu que le codage en dur du nom de la propriété était la solution la plus rapide et la plus fiable.)

Le problème avec la méthode basée sur la reflection est que c’est plutôt coûteux et pas très rapide. Bien sûr, il est beaucoup plus flexible et moins fragile en matière de refactorisation.

Cependant, cela peut vraiment nuire à la performance, surtout lorsque les choses sont appelées fréquemment. La méthode stackframe, également (je crois) a des problèmes avec CAS (par exemple, des niveaux de confiance restreints, tels que XBAP). Il est préférable de le coder en dur.

Si vous recherchez une notification de propriété rapide et flexible dans WPF, il existe une solution: utilisez DependencyObject 🙂 C’est ce pour quoi elle a été conçue. Si vous ne voulez pas prendre la dépendance, ou ne vous souciez pas des problèmes d’affinité des threads, déplacez le nom de la propriété dans une constante, et bougez! ton bien

Vous voudrez peut-être éviter complètement INotifyPropertyChanged. Il ajoute un code de comptabilité inutile à votre projet. Pensez à utiliser les contrôles de mise à jour .NET à la place.

J’ai fait quelque chose comme ça une fois en tant qu’expérience, de la mémoire cela a fonctionné correctement, et a supprimé le besoin de coder en dur tous les noms de propriété dans les chaînes. Les performances peuvent être en cause si vous créez une application serveur à haut volume, sur le bureau, vous ne remarquerez probablement jamais la différence.

 protected void OnPropertyChanged() { OnPropertyChanged(PropertyName); } protected ssortingng PropertyName { get { MethodBase mb = new StackFrame(1).GetMethod(); ssortingng 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; } } 

Comme le mot-clé nameof () est C # 6.0, il sera évalué au moment de la compilation, il aura donc la même performance que la valeur codée en dur et sera protégé contre les incompatibilités avec les propriétés notifiées.

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