.NET 4.5: INotifyPropertyChanged vereinfacht

28. März 2012

Innerhalb einer Methode herauszufinden, von wo man eigentlich aufgerufen wurde, war unter .NET bisher nur unter Umständen möglich, z.B. mit der StackTrace-Klasse. Dabei ist es für viele Anwendungsszenarien sinnvoll, schnell und möglichst automatisch den direkten Aufrufer zu kennen, z.B. zur Implementierung von INotifyPropertyChanged. Mit .NET 4.5 und den Compiler Services ist das im Handumdrehen erledigt.

Manchmal sind es die kleinen Dinge, die man in einer neuen .NET-Framework-Version entdeckt und die einen entzücken. In Silverlight und auch WPF gibt es schon immer das INotifyPropertyChanged-Interface, über das eine Klasse Änderungen an ihren Properties bekanntmachen kann, sodass sich die UI automatisch aktualisiert. Allerdings muss der Entwickler dabei den Namen des Properties als String übergeben, was sowohl aufwendig als auch fehleranfällig ist. Konkret hat das bisher z.B. so ausgesehen:

   1: public class ProductsViewModel : INotifyPropertyChanged

   2: {

   3:     private decimal _priceTotal;

   4:     public decimal PriceTotal

   5:     {

   6:         get { return _priceTotal; }

   7:         set

   8:         {

   9:             _priceTotal = value;

  10:             NotifyPropertyChanged("PriceTotal");

  11:         }

  12:     }

  13:  

  14:     public void NotifyPropertyChanged(string propertyName)

  15:     {

  16:         if (PropertyChanged != null)

  17:         {

  18:             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

  19:         }

  20:     }

  21:  

  22:     public event PropertyChangedEventHandler PropertyChanged;

  23: }

Das ist fehleranfällig gegenüber Schreibfehlern und problematisch bei Refactoring-Aufgaben, die Wartbarkeit leidet darunter. Zwar kann man sich auch seine eigene Hilfsfunktionalität schreiben, mit der man den Propertynamen als Lambda Expression übergibt und aus dem dann der Name als String per Analyse des Expression Trees extrahiert wird. Doch auch hier gibt es Nachteile, z.B. in der komplizierten und zeitaufwendigen Auswertung der Lambda Expression per Reflection. Ein Delegate zu verwenden nur um einen Propertynamen zu übergeben kann zudem für einige Entwickler verwirrend sein.

.NET 4.5 stellt der manuellen String-Übergabe ein alternatives Konzept gegenüber. Es liefert mit den neuen Compiler Services drei sogenannte Caller Information-Attribute, durch die eine Methode Informationen über den Aufrufer auslesen kann. Wichtig dabei: Nicht der Entwickler, sondern der Compiler stellt Informationen über den Aufrufer bereit! Bei den neuen Attributen handelt es sich um:

  • CallerFilePath: Liefert den vollen Pfad der Sourcedatei des Aufrufers (zur Compilezeit).
  • CallerLineNumber: Liefert die Zeilennummer des Aufrufs in der Sourcedatei des Aufrufers.
  • CallerMemberName: Liefert den Methoden- oder Property-Namen des Aufrufers.

Für unsere Zwecke von INotifyPropertyChanged ist vor allem das letzte Attribut interessant. Nutzen lässt es sich wie folgt:

   1: public class ProductsViewModel : INotifyPropertyChanged

   2: {

   3:     private decimal _priceTotal;

   4:     public decimal PriceTotal

   5:     {

   6:         get { return _priceTotal; }

   7:         set

   8:         {

   9:             _priceTotal = value;

  10:             NotifyPropertyChanged();

  11:         }

  12:     }

  13:  

  14:     public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)

  15:     {

  16:         if (PropertyChanged != null)

  17:         {

  18:             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

  19:         }

  20:     }

  21:  

  22:     public event PropertyChangedEventHandler PropertyChanged;

  23: }

Die Änderung im Code ist klein, aber entscheidend: Bei einem Aufruf von NotifyPropertyChanged() muss der Propertyname nicht mehr vom Entwickler mitgegeben werden. Stattdessen wird er durch Verwendung des CallerMemberName-Attributes vom Compiler automatisch in die NotifyPropertyChanged()-Methode injiziert und kann dann im Methodenrumpf verwendet werden. Interessant ist auch zu sehen, dass der propertyName-Parameter mit einem Default-Argument implementiert ist. Somit kann er bei Bedarf durch explizites Übergeben eines Wertes überschrieben werden, wenn dies notwendig ist (z.B. weil sich der Wert eines berechneten Properties durch Setzen eines anderen Properties ändert und sich die UI trotzdem entsprechend aktualisieren soll).

Diese Funktionalität macht natürlich nicht nur bei INotifyPropertyChanged Sinn. Auch beim Logging kann das einiges an fehlerträchtiger Programmierarbeit sparen und die Wartbarkeit steigt. Weitere Informationen finden sich u.a. in der MSDN-Doku zu INotifyPropertyChanged und in diesem guten Blogbeitrag.