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 ;-)

Technorati-Tags:
Kick it on dotnet-kicks.de

9 Responses to “Implementieren von INotifyPropertyChanged”

  1. Markus Zywitza Says:

    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.

  2. basti Says:

    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

  3. Stefan Lieser Says:

    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

  4. Sebastian Jancke Says:

    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 ;-)

  5. Stefan Liesers Blog » Blog Archive » Testen von INotifyPropertyChanged Says:

    [...] Stefan Liesers Blog « Implementieren von INotifyPropertyChanged [...]

  6. Jens Hofmann Says:

    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

  7. Dirk Rodermund Says:

    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

  8. INotifyPropertyChanged Interface | LieberLieber Software TeamBlog Says:

    [...] verwendet werden. Dazu habe ich 2 sehr gute Artikel bei Stefan Liesers Blog gefunden: 1. Implementieren von INotifyPropertyChanged 2. Testen von INotifyPropertyChanged « Unsere [...]

  9. C and it’s sharp » INotifyPropertyChanged – Varianten für die Implementierung Says:

    [...] Stefan Lieser – Implementierung direkt im ViewModel [...]