42 votes

Nom de la propriété INotifyPropertyChanged - code fixe vs réflexion?

Quel est le meilleur moyen de spécifier un nom de propriété lors de l'utilisation de 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.Substring (4) mais je suis un peu inquiet de la surcharge de réflexion.

45voto

Romain Verdier Points 8699

N'oubliez pas une chose : PropertyChanged événement est principalement consommée par les composants qui vont utiliser la réflexion pour obtenir la valeur de la propriété nommée.

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

Quand vous tirez PropertyChanged événement, en passant le nom de la propriété en tant que paramètre, vous devez savoir que l'abonné de cet événement est susceptible d'utiliser la réflexion en appelant, par exemple, GetProperty (au moins la première fois, si il utilise un cache de PropertyInfo), alors GetValue. Ce dernier appel est une invocation dynamique (MethodInfo.Invoke) de la propriété méthode de lecture, qui coûte plus de l' GetProperty dont seules les requêtes de meta-données. (À noter que la liaison de données s'appuie sur l'ensemble de la TypeDescriptor chose, mais l'implémentation par défaut utilise la réflexion.)

Alors, bien sûr, en utilisant le code de la propriété des noms lors de la cuisson PropertyChanged est plus efficace que l'utilisation de la réflexion dynamiquement pour obtenir le nom de la propriété, mais à mon humble avis, il est important pour l'équilibre de votre pensées. Dans certains cas, la charge de travail n'est pas que critique, et vous pourriez profiter d'une quelconque fortement typé événement mécanisme de tir.

Voici ce que j'utilise parfois en C# 3.0, quand les performances ne serait pas un souci :

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

Remarquez l'utilisation de l'expression de l'arbre pour obtenir le nom de la propriété, et l'utilisation de l'expression lambda comme un Expression :

FirePropertyChanged(p => p.Name);

24voto

Denis Points 4290

Dans .NET 4.5 (C # 5.0), il existe un nouvel attribut appelé - CallerMemberName qui permet d’éviter les noms de propriété codés en dur et qui empêche l’apparition de bogues si les développeurs décident de changer le nom de la propriété. En voici un exemple:

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

19voto

Orion Adrian Points 8855

La réflexion est ici surestimée, surtout depuis que INotifyPropertyChanged est appelé beaucoup . Il est préférable de coder en dur la valeur si vous le pouvez.

Si vous ne vous souciez pas de la performance, je vous conseillerais de choisir les méthodes qui nécessitent le moins de codage. Si vous pouviez faire quelque chose pour supprimer complètement le besoin de l'appel explicite, alors ce serait mieux (par exemple, AOP).

15voto

Phil Points 1467

Les performances sont impliqués dans l'utilisation de l'expression des arbres est due à la répétition de la résolution de l'expression de l'arbre.

Le code suivant utilise encore des arbres d'expression et a donc la conséquence avantages de refactoring amicale et de la dissimulation de l'amicale, mais en réalité c'est environ 40% plus rapide (très approximatif tests) que la technique habituelle qui consiste à newing un PropertyChangedEventArgs objet pour chaque notification de changement.

Il est plus rapide et évite les performances de l'arborescence d'expression parce que nous cache statique PropertyChangedEventArgs objet pour chaque propriété.

Il y a une chose dont je ne suis pas encore à faire - j'ai l'intention d'ajouter un bout de code qui vérifie les versions de débogage que le nom de la propriété fourni PropertChangedEventArgs objet correspond à la propriété dans laquelle il est utilisé à l'heure actuelle avec ce code, il est toujours possible pour le développeur de fournir le mauvais objet.

Check it out:

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

11voto

Philipp Points 151

Romain:

Je dirais que vous n'avez pas besoin de la "Personne" de paramètre en conséquence, un complètement générique extrait de code comme celui-ci devrait le faire:

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

...cependant, je préfère coller des paramètres de chaîne avec conditionnelle de validation dans les versions de Débogage. Josh Smith a posté un bel exemple à ce sujet:

Une classe de base qui implémente INotifyPropertyChanged

Cheers :) Philipp

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X