Implementieren von INotifyPropertyChanged
Wenn eine Klasse das INotifyPropertyChanged Interface implementiert muss jeder Property-Setter das PropertyChanged Event auslösen. Dabei muss der Name des Property als Event Argument übergeben werden. Eine übliche Implementierung sieht folgendermaßen aus:
public class KundeViewModel : INotifyPropertyChanged
{
private string name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name {
get { return name; }
set {
if (name == value) {
return;
}
name = value;
OnPropertyChanged("Name");
}
}
private void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
An dieser Form der Implementierung gefällt mir der String Parameter beim Aufruf von OnPropertyChanged nicht. Die Gefahr dabei ist, dass beim Refactoring der Name des Propertiy geändert wird, man jedoch vergisst den String Parameter ebenfalls anzupassen.
Abhilfe schafft eine Lambda Expression. Der Name des Property wird folgendermaßen übergeben:
OnPropertyChanged(x => x.Name);
Die Lambda Expression steht zur Laufzeit in Form eines Expression Tree zur Verfügung so dass man den Namen des übergebenen Property ermitteln kann. Um den Expression Tree zu erhalten muss man den Parameter der Methode vom Typ Expression<Func<T, R>> definieren.
Hier die vollständige Implementierung:
public class KundeViewModel : INotifyPropertyChanged
{
private string name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name {
get { return name; }
set {
if (name == value) {
return;
}
name = value;
OnPropertyChanged(x => x.Name);
}
}
private void OnPropertyChanged<T>(Expression<Func<KundeViewModel, T>> projection) {
OnPropertyChanged(PropertyName(projection));
}
private static string PropertyName<T>(Expression<Func<KundeViewModel, T>> projection) {
MemberExpression memberExpression = (MemberExpression) projection.Body;
return memberExpression.Member.Name;
}
private void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Look Ma, no strings
November 12th, 2008 at 13:09
Da ich mich mit mit Lambdas noch nicht wirklich beschäftigt habe, ist es zwar ein interessanter Post, aber ich frage mich, ob das nicht generischer geht.
Eventuell über einen Helper, der für alle Implementation von INotifyPropertyChanged zur Verfügung steht.
Besser wäre noch eine Lösung, die den Event über AOP bzw. einen DynamicProxy auslöst, damit wieder einfach
public string Name { get; set; }
geschrieben werden kann.
November 12th, 2008 at 18:23
hi stefan,
die idee ist gut. auch das obige kommentar würde mir gut gefallen, so dass es eben noch generischer geht. fällt mir gerade aber nich ein.
habe aber noch eine sache anzumerken. ist vielleicht ein bisschen kleinkariert, aber: denk doch an die performance!
innem kleinen test in dem 10000 mal der propertyevent geschmissen wurde, konnte ich im profiler diesen zeitverbauch sehen:
nach altvätersitte mit string: 17ms
nach lieser-art: 780ms
was machen wir denn da?
der basti
November 12th, 2008 at 20:02
Hallo Basti,
die Property-Setter werden von der UI verwendet. Das Laufzeitverhalten dürfte daher kein Problem sein. Falls doch ist “function memoization” dein Freund
Viele Grüße,
Stefan
November 13th, 2008 at 23:46
Gute Idee, schreit aber noch einer AOP-Lösung. Dies gilt aber für INotifyPropertyChanged generell. Gibt es meine ich auch schon mit PostSharp, allerdings ohne deine schönen Lamdas
November 15th, 2008 at 16:34
[...] Stefan Liesers Blog « Implementieren von INotifyPropertyChanged [...]
November 15th, 2008 at 19:07
Ich würde mir zumindest die Schreibarbeit gering halten wenn ich es öffter implementieren würde. Hierzu würd ich eine Extension-Method einsetzen:
public static class INotifyPropertyChangedExtender
{
public static string GetMemberName(this T1 instance, Expression<Func> projection)
{
return ((MemberExpression) projection.Body).Member.Name;
}
}
Benutzung:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name;}
set
{
if (_name == value)
return;
_name = value;
OnPropertyChanged(this.GetMemberName(x => x.Name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Jemand noch eine bessere Idee?
Grüße,
Jens
November 16th, 2008 at 0:26
Grundsätzlich begrüße ich auch die Ansätze, die Refactoring unfreundlichen magic Strings gegen Lambda Expressions auszutauschen.
Ich würde jedoch, wie Jens es beschreibt, eher mit einer Extension Method / statischen Helperklasse arbeiten. Aber was den Setter angeht, gefällt mir der Stil den Du gewählt hast deutlich besser:
OnPropertyChanged(x => x.Name);
Kurz, prägnant und nicht so verbose!
BTW: Ich verfolge z. Z. ähliche Ansätz in einem neuen ASP.NET MVC Projekt, was ich gerade angefangen habe.
Gruß
Dirk
December 10th, 2008 at 10:03
[...] verwendet werden. Dazu habe ich 2 sehr gute Artikel bei Stefan Liesers Blog gefunden: 1. Implementieren von INotifyPropertyChanged 2. Testen von INotifyPropertyChanged « Unsere [...]
September 5th, 2009 at 10:31
[...] Stefan Lieser – Implementierung direkt im ViewModel [...]