Home > Microsoft .NET > Erweiterung von INotifyPropertyChanged (PostSharp)

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.
kick it on dotnet-kicks.de

KategorienMicrosoft .NET Tags: ,
  1. 5. April 2012, 00:13 | #1

    Also ich weiss nicht, der Aufwand bei PostSharp scheint mir ziehmlich hoch zu sein und der Code sieht alles andere als leserlich aus. Mit Loom.NET hast Du dasselbe Ergebnis mit folgenden Zeilen:

        public class NotifyPropertyChanged : AspectAttribute
        {
            [Access(Advice.After), IncludeAll]
            public void PropertyChangedAdvice([JPContext] Context ctx, T obj)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(ctx.Instance, new PropertyChangedEventArgs(ctx.CurrentMethod.Name.Substring(4)));
                }
            }
     
            [Introduce(typeof(INotifyPropertyChanged))]
            public event PropertyChangedEventHandler  PropertyChanged;
        }
  2. 8. April 2012, 21:17 | #2

    Ich gebe zu, dass sieht merklich knackiger aus. Ich hoffe, dass ich das die nächsten Tage mal Testen kann. Ich finde zwar schon, dass PostSharp eine wahnsinnig mächtige Lösung ist, das Verstehen und schwierige Erweitern (wenn man’s nicht täglich macht), ist schon lange ein Punkt auf meiner Liste…

  3. 20. April 2012, 22:08 | #3

    @Wolle
    Sieht gut aus, aber ist leider nur für .NET 4? In allen Projekten mit Einsatzpotential verwenden wir aktuell noch .NET 2-3.5. Schade, aber ein weiterer Test erübrigt sich dadurch. Trotzdem Danke für den Tipp!

  4. 24. April 2012, 15:16 | #4

    @Mario Noack
    Naja, man kann ja auch die Version 2.x benutzen, die ist für .NET 2.0 und hatte diese Features auch schon.

  1. Bisher keine Trackbacks