Dieser Artikel entstand als Ableger aus dem [Artikel] Implementierung von IDataErrorInfo.
Die INotifyPropertyChanged-Schnittstelle INotifyPropertyChanged-Schnittstelle (System.ComponentModel) hilft bei der Benachrichtigung anderer Instanzen bzgl. veränderter Properties. Besonders in WPF hilft diese Schnittstelle bei der Trennung zwischen Logik und UI, da erst durch die Implementierung dieser Schnittstelle die Anzeige bei Änderungen aktualisiert wird.
Die Schnittstelle hat nur einen Member:
event PropertyChangedEventHandler PropertyChanged;
Zur empfohlenen Implementierung von Events gehört dann auch eine entsprechende Aufruf-Methode:
protected void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler tempHandler = PropertyChanged;
if (tempHandler != null)
tempHandler(this, new PropertyChangedEventArgs(propertyName));
}
Siehe dazu auch:
- Gewusst wie: Implementieren von Schnittstellenereignissen (C#-Programmierhandbuch)
- Gewusst wie: Veröffentlichen von Ereignissen, die den .NET Framework-Richtlinien entsprechen (C#-Programmierhandbuch)
- [Lösung] Problem mit EventHandler
Basis-Implementierung
Nun ein Beispiel, welches die Basis-Funktionalität zur Wiederverwendung in einer abstrakten Klasse anbietet:
public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler tempHandler = PropertyChanged;
if (tempHandler != null)
tempHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
Hier eine weitere mögliche Standardimplementation von INotifyPropertyChanged.
Übergabe der Property-Namen
Kommen wir nun zum Aufruf des Events. Dieser findet in der Regel innerhalb der Setter statt, wenn sich der Wert tatsächlich verändert:
private int intProperty;
public int IntProperty
{
get
{
return intProperty;
}
set
{
if (intProperty != value)
{
intProperty = value;
OnPropertyChanged("IntProperty");
}
}
}
Ein Tippfehler oder vergessene Passagen beim Refactoring, und schon geht die Benachrichtigung ins Leere.
Wir brauchen also eine Alternative zu den fixen String-Parametern. Hier gibt es nun drei Möglichkeiten:
- Lambda-Expressions
- StackFrame()-GetMethod-Methode (System.Diagnostics)
- MethodBase.GetCurrentMethod-Methode (System.Reflection)
Im ersten Fall würde anstatt des Property-Namen eine Lambda-Expression an eine entsprechende Prozedur übergeben, welche den Namen der Property aus dem ExpressionBody extrahiert.
Da der Name als Ausdruck übergeben wird, wird er nun auch beim Refactoring geändert bzw. der Compiler liefert eine entsprechende Fehlermeldung, wenn der Name falsch ist.
Nachteilig ist, dass diese Funktionalität in dieser Form nicht bei Properties in abgeleiteten Klassen funktioniert und bei Copy&Paste in andere Setter auch gültig bleibt, wenn man den Namen nicht anpasst.
Im zweiten Fall würde eine Methode aufgerufen werden, in welcher der Name der Property mit Hilfe des StackFrame ermittelt würde.
Diese Methode funktioniert dann aber nur, wenn sie innerhalb des jeweiligen Property-Setters aufgerufen wird.
Zudem wird im [Artikel] Attribute zur Prüfung von Properties verwenden noch ein Problem bzgl. Inlining angesprochen.
Im dritten Fall würde einfach eine .NET-Methode benutzt: System.Reflection.MethodBase.GetCurrentMethod(), welche ein Metadatenobjekt der aufrufenden Methode zurückliefert, u.a. mit der Property Name, welche den Namen der Methode enthält.
Alle Methoden führen zum gewünschten Ergebnis führen, allerdings haben die ersten beiden gravierende Nachteile und sind zudem deutlich lansgamer.
Vorteilhaft an der MethodInfo-Methode ist zudem die einfache, da immer gleiche Implementierung einer Zeile bei Verwendung einer Basis-Klassen mit der entsprechenden Funktionalität, somit sind auch Copy&Paste-Fehler ausgeschlossen.
FAZIT
Somit bleibt hier als einfache und sichere Variante lediglich die MethodInfo-Methode, da diese Refactoring-sicher und an jeder Stelle einsetzbar ist, und das Problem der Übergabe der Namen als Parameter ist gelöst.
Hier nun der Code für eine mögliche Referenzimplementierung:
using System.ComponentModel;
using System.Reflection;
public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
#region INotifyPropertyChanged Member
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Called when property changed.
/// </summary>
/// <param name="name">The name.</param>
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler tempHandler = PropertyChanged;
if (tempHandler != null)
tempHandler(this, new PropertyChangedEventArgs(name));
}
/// <summary>
/// Called when property changed.
/// </summary>
/// <param name="methodBase">The method base.</param>
protected void OnPropertyChanged(MethodBase methodBase)
{
OnPropertyChanged(methodBase.Name.Substring(4));
}
#endregion
}
Der Aufruf erfolgt dann so:
OnPropertyChanged(MethodInfo.GetCurrentMethod());
Wer noch mehr Informationen haben will, sollte sich den Artikel INotifyPropertyChanged – Varianten für die Implementierung durchlesen, dort werden weitere Varianten zur INotifyPropertyChanged-Implementierung vorgeschlagen, u.a. auch eine aspektorientierte Möglichkeit.
Im Anhang befindet sich noch eine Beispiel-Anwendung, welche Performance-Tests mit allen Varianten durchführt.
Abschließende Worte
Ganz herzlich bedanken möchte ich mich an dieser Stelle bei winSharp93, herbivore, JuyJuka, talla und Programmierhans für Ihre hilfreichen Kommentare und Anmerkungen bedanken.