2009-07-14 8 views
10

Il doit être un événement assez courant pour modifier le nom d'une propriété et attendre que la fonctionnalité Renommer dans Visual Studio prenne en charge tous les changements de nom nécessaires, à l'exception du nom de propriété de l'événement PropertyChanged de INotifyPropertyChanged. Existe-t-il un meilleur moyen de le faire typer fortement de sorte que vous n'avez pas besoin de se souvenir de le renommer manuellement?Existe-t-il une méthode fortement typée pour faire des événements PropertyChanged en C#?

+0

(exemple ajouté selon la demande/le commentaire) –

+1

Jetez un oeil à cet article. http://weblogs.asp.net/dwahlin/archive/2009/07/07/validating-properties-in-silverlight-classes.aspx Si vous créez une classe de base qui implémente INotifyPropertyChanged et en dérive, peut-être vous pouvez l'utiliser. –

+1

voir http://stackoverflow.com/questions/1329138/how-to-make-databinding-type-safe-and-support-refactoring/1333874#1333874 pour une manière vérifiée par le compilateur d'implémenter INotifyPropertyChanged. Éviter d'avoir les noms de propriété comme une chaîne magique. –

Répondre

8

Modifier: nameof est arrivé dans C# 6. Yay!


Il n'y a pas nameof/infoof etc; c'est beaucoup discuté, mais c'est ce que c'est.

Il existe un moyen de le faire en utilisant des expressions lambda dans .NET 3.5 (et en analysant l'arbre d'expression), mais en réalité cela ne vaut pas la surcharge. Pour l'instant, je m'en tiendrai aux cordes (et aux tests unitaires si vous êtes déterminé à ne pas le casser).


using System; 
using System.ComponentModel; 
using System.Linq.Expressions; 
using System.Reflection; 
class Program : INotifyPropertyChanged { 
    public event PropertyChangedEventHandler PropertyChanged; 
    static void Main() { 
     var p = new Program(); 
     p.PropertyChanged += (s, a) => Console.WriteLine(a.PropertyName); 
     p.Name = "abc"; 
    } 
    protected void OnPropertyChanged<T>(Expression<Func<Program, T>> property) { 
     MemberExpression me = property.Body as MemberExpression; 
     if (me == null || me.Expression != property.Parameters[0] 
       || me.Member.MemberType != MemberTypes.Property) { 
      throw new InvalidOperationException(
       "Now tell me about the property"); 
     } 
     var handler = PropertyChanged; 
     if (handler != null) handler(this, 
      new PropertyChangedEventArgs(me.Member.Name)); 
    } 
    string name; 
    public string Name { 
     get{return name;} 
     set { 
      name = value; 
      OnPropertyChanged(p=>p.Name); 
     } 
    } 
} 
+0

Je vais probablement m'en tenir aux chaînes, mais pour les kicks, pourriez-vous donner un exemple de code court sur la façon d'analyser l'arbre d'expression? – Davy8

+0

Pourquoi utilisez-vous 'Expression >' et 'p => p.Name' au lieu de' Expression > 'et'() => Nom'? – Svish

+1

Tout irait bien, mais la version affichée indique clairement que nous recherchons une propriété sur l'instance - pas seulement quelque chose de aléatoire. –

0

Pas une réponse à votre question, mais si vous faites un clic droit-> Refactor-> Renommer une propriété, il peut également renommer les chaînes correspondantes, y compris toutes les chaînes qui correspondent au nom de votre propriété.

Oui, ça peut être un peu dangereux.

+0

Même renommer dans les commentaires est dangereux. J'ai foiré un tas de documentation XML dans un projet assez volumineux en supposant que cette fonctionnalité limiterait la portée aux commentaires sur/dans l'élément de code qui est renommé. – snarf

-1

Le PropertyChangedEventArgs ne prend qu'un seul constructeur, ce qui nécessite le nom de la propriété sous forme de chaîne. Donc, essentiellement, ne pas utiliser INotifyPropertyChanged signifie qu'à un certain niveau, que ce soit haut ou bas dans votre architecture, vous devrez travailler avec une chaîne et renommer manuellement.

2

La solution la plus simple est de regarder la trace de la pile et supprimer toute référence completly explicite à la propriété.

public String Name 
{ 
    get { return this.name; } 
    set 
    { 
     if (value != this.name) 
     { 
      this.RaisePropertyChanging(); 
      this.name = value; 
      this.RaisePropertyChanged(); 
     } 
    } 
} 
private String name = null; 

private void RaisePropertyChanged() 
{ 
    String propertyName = 
     new StackTrace().GetFrame(1).GetMethod().Name.SubString(4); 

    PropertyChangedEventHandler handler = this.PropertyChanged; 
    if (handler != null) 
    { 
     handler(new PropertyChangedEventArgs(propertyName)); 
    } 
} 

Le code dérive le nom de la propriété à travers la trace de la pile à partir de la méthode de caling - qui est la méthode de définition de propriété nommée set_<PropertyName>. Si le compilateur ne suit plus cette convention de dénomination, le code rompt.

L'autre solution consiste à dériver le nom de la propriété d'une expression lambda.

public static String GetPropertyNameFromLambdaExpression<TObject, TProperty>(
    Expression<Func<TObject, TProperty>> expression) 
{ 
    return ((MemberExpression)expression.Body).Member.Name; 
} 

Par exemple

GetPropertyNameFromLambdaExpression<String, Int32>(s => s.Length) 

retournera "Length" comme exspected. Une version de production du code exige vraiment des vérifications supplémentaires et une meilleure intégration dans le reste du code. Par exemple, il est possible d'utiliser l'inférence de type pour les arguments génériques.

MISE À JOUR

Et il y a une troisième solution - vous pouvez utiliser MethodBase.GetCurrentMethod() dans un getter de la propriété ou setter pour obtenir le nom de la méthode setter ou getter.

public String Name 
{ 
    get { return this.name; } 
    set 
    { 
     if (value != this.name) 
     { 
      String propertyName = MethodBase.GetCurentMethod().Name.SubString(4); 

      this.RaisePropertyChanging(propertyName); 
      this.name = value; 
      this.RaisePropertyChanged(propertyName); 
     } 
    } 
} 
private String name = null; 
+1

Cette solution est plus fragile que de simplement définir le nom de la propriété sous forme de chaîne. – Charlie

+0

Cela semble intéressant. Sans vérifier la documentation, je n'arrive pas à comprendre à quoi sert l'appel à SubString. Est-ce que GetMethod(). Name retourne quelque chose d'étrange? –

+0

Une propriété est implémentée par deux méthodes. public MyType MyProperty {get; ensemble; } est implémenté comme public void set_MyProperty (valeur MyType) {} et public MyType get_MyProperty(). Vous devez donc supprimer set_ et get_ du nom de la méthode retournée pour obtenir le nom de la propriété. –

1

En théorie, vous pouvez utiliser MethodBase.GetCurrentMethod(). Name.Substring (4) à partir du setter de propriété. Malheureusement, la recherche Google révèle que it seems to have a significant performance impact.Deux autres éléments à prendre en compte:

  • JIT inlining peut avoir un impact inattendu. (stackoverflow.com/questions/616779/can-i-check-if-the-c-compiler-inlined-a-method-call)
  • En théorie, l'appel IL à MethodBase.GetCurrentMethod() pourrait être trivialement remplacé par le JIT à l'exécution avec une instruction ldtoken suivie d'un appel à MethodBase.GetMethodFromHandle(), ce qui serait très rapide. Je suppose que les utilisateurs n'ont tout simplement pas exprimé le besoin de cela. (msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldtoken.aspx)
  • Complètement mon opinion ici, mais je pense que ce serait bien d'avoir fieldof() et methodof() opérateurs en C#. Je crois que cela améliorerait grandement la fiabilité des outils d'analyse/de refactorisation du code dans les projets qui nécessitent cette capacité.
0

Vous devriez vérifier ce blog post. Il vous donne la possibilité de le faire:

string propertyName = TypeHelper.GetPropertyName<User>(u => u.LastProjectCode); 

PropertyInfo property1 = TypeHelper.GetProperty((SomeClass o) => o.InstanceProperty.Length); 

PropertyInfo property2 = TypeHelper.GetProperty(() => SomeClass.StaticProperty.Length); 

renomme dans Visual Studio/ReSharper/Refactor Pro devrait fonctionner pour vous alors.

6

C# 5 semblent avoir une solution. Avec un CallerMemberName attribute qui peut être utilisé avec les paramètres (One example on the net).

class Employee : INotifyPropertyChanged 
{ 
    private string _Name; 
    public string Name 
    { 
     get { return _Name; } 

     set 
     { 
      _Name = value; 
      RaisePropertyChanged(); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    private void RaisePropertyChanged([CallerMemberName] string caller = "") 
    { 
     var temp = PropertyChanged; 

     if (temp != null) 
     { 
      temp(this, new PropertyChangedEventArgs(caller)); 
     } 
    } 
} 
+0

Wow! Le vôtre semble être le plus cool. – VivekDev

Questions connexes