300 words
2 minutes
INotifyPropertyChanged oraz Expresssion Trees

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:

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.