Erweiterung von INotifyPropertyChanged (PostSharp)
Mit PostSharp bekommt man eine mächtige Möglichkeit, Code mittels Attributen aufzupeppen. So wird man auch in die Lage versetzt, wiederkehrenden Code zu vermeiden. Üblicherweise sieht nämlich beispielsweise eine Implementation von System.ComponentModel.INotifyPropertyChanged wie folgt aus:
public class Test: System.ComponentModel.INotifyPropertyChanged { private string _myProp; public string MyProp { get { return _myProp; } set { if (_myProp == value) return; _myProp = value; OnPropertyChanged("MyProp"); } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } |
Verwendet man jedoch PostSharp als Referenz in seinem Projekt und die Klasse NotifyPropertyChangedAttribute aus
{ProgramFiles}\PostSharp 2.0\Samples\.NET Framework 3.5\CSharp\NotifyPropertyChanged
kann man seinen Code merklich verkürzen:
[NotifyPropertyChanged] public class TestBase { public string MyProp { get; set; } } |
Problem
Leider ergibt letztere Implementation das Problem, dass ich die Kontrolle über das Auslösen von Events vollständig an das Attribut NotifyPropertyChanged abgebe. Da der Code für das Event nachträglich in das Compilat injiziert wird, ergibt sich keine Möglichkeit, das Event PropertyChanged manuell auszulösen, da .NET das Auslösen von Events nur innerhalb der implementierenden Klasse erlaubt. Versuche, Zugriff auf das Event durch Vorimplementierung des Interfaces zu erhalten, helfen leider auch nicht, da der durchs Attribut erzeugte Code in einer Property der Klasse zusätzlich angelegt wird. Dies kann man leicht mit dem Reflektor prüfen:
public string MyProp { [CompilerGenerated] get { return this.<get_MyProp>(); } [CompilerGenerated] set { LocationInterceptionArgsImpl<string> CS$0$1 = new LocationInterceptionArgsImpl<string>(this, Arguments.Empty); CS$0$1.Location = <>z__Aspects.l3; CS$0$1.TypedBinding = <MyProp>c__Binding.singleton; CS$0$1.TypedValue = value; this.<>z__aspect0.OnPropertySet(CS$0$1); } } |
Das Setzen der per Attribut erweiterten Property wird beispielsweise komplett auf z__apect0 umgeleitet. Und an genau dieses z__apect0 ist ja zur Entwurfszeit noch nicht verfügbar (das ist ja der Sinn des Attributes).
Umgehen konnte ich das Problem mit einer kleiner Erweiterung des NotifyPropertyChangedAttribute:
[ImportMember("ManualPropertyChanged", IsRequired = false, Order = ImportMemberOrder.AfterIntroductions)] public Event<PropertyChangedEventHandler> ManualPropertyChanged; public override void RuntimeInitializeInstance() { base.RuntimeInitializeInstance(); if (ManualPropertyChanged != null) ManualPropertyChanged.Add((sender, args) => { if (PropertyChanged != null) PropertyChanged(Instance, args); }); } |
Bietet meine Klasse, welche das NotifyPropertyChangedAttribute ausweist, folgendes Ereignis an:
public event PropertyChangedEventHandler ManualPropertyChanged; |
so abonniert das Attribut dieses Ereignis und sorgt beim Auslösen von ManualPropertyChanged automatisch für eine Weiterleitung an das Ereignis PropertyChanged.