Anch’io nei miei progetti WPF per rendere testabile la logica della mia applicazione senza rinunciare alla pontenza del DataBinding utilizzo (anche) il Pattern Model-View-ViewModel. L’interfaccia INotifyPropertyChanged gioca un ruolo fondamentale nell’implementazione di questo pattern quindi ne faccio un uso massiccio. Quello che non mi è mai piaciuto di questa interfaccia è l’uso delle stringe per indicare qual è la proprietà modificata, vediamo un semplice esempio dell’uso di INotifyPropertyChanged:
1: public class Person : INotifyPropertyChanged {
2: public event PropertyChangedEventHandler PropertyChanged;
3:
4: private string firstName = String.Empty;
5: public string FirstName {
6: get { return firstName; }
7: set {
8: if (firstName != value) {
9: firstName = value;
10: RaisePropertyChanged("FirstName");
11: }
12: }
13: }
14:
15: private void RaisePropertyChanged(string propertyName) {
16: if (PropertyChanged != null)
17: PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
18: }
19: }
Se cambio il nome della proprietà FirstName Visual Studio si accorge del cambiamento e mi permette con un solo click di rinominare la proprietà ovunque venga utilizzata, però questo sistema di refactoring non è abbastanza smart da modificare la stringa FirstName (riga 10). Grazie alla Static Reflection possiamo eliminare questo problema, il “trucco” sta nel passare al metodo RaisePropertyChanged la proprietà come tipo Expression<Func<T>>, convertire successivamente il Body nel tipo MemberExpression ed in fine recuperare il nome tramite le proprietà Member. Risultato:
1: public class Person : INotifyPropertyChanged {
2: public event PropertyChangedEventHandler PropertyChanged;
3:
4: private string firstName = String.Empty;
5: public string FirstName {
6: get { return firstName; }
7: set {
8: if (firstName != value) {
9: firstName = value;
10: RaisePropertyChanged(() => FirstName);
11: }
12: }
13: }
14:
15: private void RaisePropertyChanged<T>(Expression<Func<T>> property) {
16: var expression = property.Body as MemberExpression;
17: var member = expression.Member;
18: if (PropertyChanged != null)
19: PropertyChanged(this, new PropertyChangedEventArgs(member.Name));
20: }
21: }
Adesso il nostro codice supporta pienamente il
rename refactoring. Come ultimo passo possiamo incapsulare la logica di manipolazione dei tipi
Expression in un
Extension methods rendendo il codice molto più leggibile:
1: public static class PropertyExtensions {
2: public static PropertyChangedEventArgs CreateChangeEventArgs<T>(this Expression<Func<T>> property) {
3: var expression = property.Body as MemberExpression;
4: var member = expression.Member;
5: return new PropertyChangedEventArgs(member.Name);
6: }
7: }
8:
9: public class Person : INotifyPropertyChanged {
10: public event PropertyChangedEventHandler PropertyChanged;
11:
12: private string firstName = String.Empty;
13: public string FirstName {
14: get { return firstName; }
15: set {
16: if (firstName != value) {
17: firstName = value;
18: RaisePropertyChanged(() => FirstName);
19: }
20: }
21: }
22:
23: private void RaisePropertyChanged<T>(Expression<Func<T>> property) {
24: if (PropertyChanged != null)
25: PropertyChanged(this, property.CreateChangeEventArgs());
26: }
27: }