Invest in people before investing in tools

Il blog di Matteo Baglini
posts - 118, comments - 103, trackbacks - 2327

Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

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

Print | posted on venerdì 28 novembre 2008 15.06 | Filed Under [ .NET ]

Feedback

Gravatar

# Re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

togo, molto togo! :-D

.m
28/11/2008 16.20 | Mauro Servienti
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

Fantastico!!!
Grazie :D
28/11/2008 22.25 | Dario Santarelli
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

Io non sono in grado (non ho la competenza tecnica necessaria) di valutare cos'è meglio, ma a me piace di più la soluzione proposta qui (jeffhandley.com/.../...ching-for-a-better-way.aspx), visto che posso scrivere in maniera ancora più concisa:

public NotifyPropertyChanged BirthYear { get; set; }

col quale ottengo anche il rename refactoring. Mi fai sapere cosa ne pensi? grazie.
29/11/2008 10.47 | Nicolò Carandini
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

Ops... il filtro di pubblicazione dei commenti mi toglie i segni di minore e maggiore, quindi nel mio commento precedente mi ha tolto un pezzo nel codice. ci riprovo:

public NotifyPropertyChanged [MINORE_DI] int [MAGGIORE_DI] BirthYear { get; set; }

(è orribile, ma penso si capisca :-) )
29/11/2008 10.51 | Nicolò Carandini
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

Ti rispondo con una semplice domana, hai mai provato il codice di quel post?
29/11/2008 11.53 | Matteo Baglini
Gravatar

# Mimando le Dependency Property

Mimando le Dependency Property
26/07/2009 8.47 | [ topics . it ]
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

non sono riuscito a trasformare parti del codice in vb.net
suggerimenti ?
grazie
Flavio
28/07/2009 15.11 | FLAVIO
Gravatar

# Implementazione

Implementazione
03/08/2009 7.25 | Corrado's BLogs
Gravatar

# Yet another INotifyPropertyChanged implementation

Yet another INotifyPropertyChanged implementation
03/08/2009 13.22 | TheDuzBlog! ;-p
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

Ciao Flavio,
puoi dirmi quali sono le parti di codice che non riesci a trasformare?
Hai già provato con un convertitore automatico come questo?
www.developerfusion.com/.../csharp-to-vb/
03/08/2009 14.30 | Matteo Baglini
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

Veramente gran bel post, non capita tutti i giorni di incappare in soluzioni davvero utili ed allo stesso tempo eleganti! Per me che faccio un riuso massiccio dei miei stessi codici in altri contesti, il refactor era quasi un problema :|. Thanks a lot ;) Dave
10/08/2009 12.30 | Daevil
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

Scusa se arrivo adesso ma riguardo alla mia domanda prima delle ferie
era questa parte
private void RaisePropertyChanged<T>(Expression<Func<T>> property) {
var expression = property.Body as MemberExpression;
var member = expression.Member;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(member.Name));
}

grazie
Flavio
06/09/2009 20.07 | FLAVIO
Gravatar

# re: Pattern Model-View-ViewModel, INotifyPropertyChanged, Static Reflection e Extension methods

I have implemented in VB.Net a similar approach using reflection.
Basically I obtain property names analizing the current call stack into a procedure which is calling in the "property set method" without indicate any parameter.
Trace.GetFrame(0).GetMethod.Name instruction gets the name of the method in top of call stack. This method should be a "set method" named set_XXX on XXX is the property name.

Code Sample:
Public Class SampleObject
Implements INotifyPropertyChanged

Sub New(ByVal p1 As String)
Me.P1 = p1
End Sub


Dim oP1 As String
Public Property P1() As String
Get
Return oP1
End Get
Set(ByVal value As String)
oP1 = value
RaisePropertyChanged()
End Set
End Property

Public Sub RaisePropertyChanged()
Const SET_METHOD_PREFIX As String = "set_"

Dim Trace As StackTrace = New StackTrace(StackTrace.METHODS_TO_SKIP + 1)
Dim SetMethodPropertyName As String = Trace.GetFrame(0).GetMethod.Name

If Left(SetMethodPropertyName, SET_METHOD_PREFIX.Length) = SET_METHOD_PREFIX Then
Dim PropertyName As String = Right(SetMethodPropertyName, SetMethodPropertyName.Length - SET_METHOD_PREFIX.Length)

RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
Else
'<falta c="Error no se reconoce el formato del metodo">
End If
End Sub

Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged


Public Overrides Function ToString() As String
Return Me.P1
End Function
End Class

Thanks.
07/01/2010 17.55 | Santiago Regojo

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 1 and 1 and type the answer here:

Powered by: