Interfejs INotifyPropertyChangedPropertyChanged znany jest prawdopodobnie każdemu programiście C#. Interfejs ten zawiera jedynie jedno zdarzenie
|
public event PropertyChangedEventHandler PropertyChanged; |
które powinniśmy odpalić w momencie gdy dana właściwość zostanie przez nas zmieniona. Dzięki temu dowolny obiekt, który będzie nasłuchiwał zmian danej właściwości zostanie o tym poinformowany. Przykładowa klasa implementująca ten interfejs może wyglądać w następujący sposób:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private string viewModelName; public string ViewModelName { get { return viewModelName; } set { viewModelName = value; NotifyPropertyChanged("ViewModelName"); } } } |
Jak widać została stworzona funkcja pomocniczna
|
public void NotifyPropertyChanged(string propertyName) |
którą wywołujemy w setterze danej właściwości, podając w parametrze nazwę tej właściwości.
Niestety podejście takie jest bardzo podatne na błędy. Po pierwsze bardzo łatwo można przekręcić nazwę właściwości, a co gorsze rozwiązanie takie nie jest uwzględniane podczas refactoringu. Dlatego też w celu poprawienia naszego rozwiązania skorzystamy z mechanizmów dostarczanych przez Expression Trees.
Po pierwsze zmieniamy funkcję
|
public void NotifyPropertyChanged(string propertyName) |
na
|
public void NotifyPropertyChanged(Expression<Func<object>> propertyExpression) { if(PropertyChanged == null) return; PropertyChanged(this, new PropertyChangedEventArgs(GetPropertyName(propertyName))); } |
funkcja pomocnicza GetPropertyName ma następującą postać
|
private string GetPropertyName(Expression<Func<object>> propertyExpression) { var unaryExpression = propertyExpression.Body as UnaryExpression; var memberExpression = unaryExpression == null ? (MemberExpression)propertyExpression.Body : (MemberExpression)unaryExpression.Operand; var propertyName = memberExpression.Member.Name; return propertyName; } |
Teraz wystarczy prosty zapis
|
NotifyPropertyChanged(()=> ViewModelName) |
który odwołuje się bezpośrednio do właściwości a nie jej nazwy. Dzięki temu kod w całości zostanie zrefaktoryzowany. Rozwiązanie to jest teoretycznie odrobinę wolniejsze od używania stringów, jednak w praktyce jest to niezauważalne.
Można również funkcję
|
string GetPropertyName(Expression<Func<object>> propertyExpression) |
wyciągnąc do osobnej klasy i zrobić z tej funkcji extension method.
|
public static string GetPropertyName(this object obj, Expression<Func<object>> propertyExpression) { var unaryExpression = propertyExpression.Body as UnaryExpression; var memberExpression = unaryExpression == null ? (MemberExpression)propertyExpression.Body : (MemberExpression)unaryExpression.Operand; var propertyName = memberExpression.Member.Name; return propertyName; } |
Od teraz wystarczy, że zaimportujemy odpowiedni namespace i możemy wykorzystać naszą funkcję w każdej klasie.