AOP in C# met PostSharp: INotifyPropertyChanged voorbeeld

 
25 november 2007

Zoals de kop van dit artikel doet vermoeden is dit een ietwat technisch verhaal. Ik zal dan ook maar beginnen met een klein stukje context. Voor aspecten in software die op meerdere punten aangrijpen (zgn. croscutting concerns) is er een techniek die Aspect Oriented Programming genoemd wordt. Ik vooronderderstel dat de lezer van dit artikel een klein beetje op de hoogte is van wat de werking van dit concept (in Java al langer bekend van o.a. AspectJ), anders verwijs ik graag naar wikipedia.

Een vaak gebruikt voorbeeld bij ter illustratie van AOP is logging van methode calls. Persoonlijk vind ik dat een goeie, echter het is inmiddels zo afgezaagd. Vandaar dat ik in deze post een korte uitleg geef hoe je met Postsharp LAOS een advice kan schrijven dat setters afvangt en zogenaamde Changed events opgooit – zonder zelf alle setters langs te hoeven gaan. INotiftyPropertyChanged is een interface die geimplementeerd moet worden door objecten die gebruikt worden in databound controls. Het wil zoveel zeggen als dat voor elke (significante) setter die uitgevoerd wordt op een object er een PropertyChanged event afgevuurd moet worden (met als argument de propertynaam) zodat alle databound controls zichzelf kunnen bijwerken.

Laat ik beginnen met uitleggen dat ik al mijn domeinobjecten die het hier betreft afleid van een DomainObject class – een zgn. Layer Supertype pattern. Dit doe ik onder andere om de code die nodig is om verschillende gebruikte interfaces te implementeren op een enkele plek te houden. Ik breid deze DomainObject class uit met de volgende methode die mij in staat stelt om veilig de events te kunnen gooien:

        public void OnPropertyChanged(string property)
        {
            if(PropertyChanged!=null)
               PropertyChanged(this,
               new PropertyChangedEventArgs(property));
        }

Zo gezegd, zo gedaan. Vervolgens wil ik dat in alle setters van afgeleide objecten de OnPropertyChanged uitgevoerd wordt. Neem bijvoorbeeld een setter van een Naam property in een willekeurige class:

        set
        {
             if ( naam != value )
             {
                 naam = value;
                 OnPropertyChanged ( "Naam" );
             }
        }

Vervelend om te schrijven – dit is veel herhalende code. Ik wil dus graag het mechanisme van interception gebruiken om de setters af te vangen en na het wijzigen van de waarde het event op te gooien. Eigenlijk ontzettend simpel, door de volgende class toe te voegen (en referenties naar PostSharp.Public en PostSharp.Laos natuurlijk):

    [Serializable]
    class NotifyingAspect : PostSharp.Laos.OnMethodBoundaryAspect
    {
        public override void OnExit(PostSharp.Laos.MethodExecutionEventArgs eventArgs)
        {
            base.OnExit(eventArgs);
            if (eventArgs.Method.Name.StartsWith("set_"))
            {
                Console.WriteLine("Setter: " + eventArgs.Method.Name);
                if (eventArgs.Instance.GetType().BaseType.Name =="DomainObject`1"))
                {
                    (eventArgs.Instance).
                        GetType().
                        GetMethod("OnPropertyChanged").
                        Invoke(
                            eventArgs.Instance,
                            new object[] { eventArgs.Method.Name.Replace("set_", "") }
                        );
                }
            }
        }
    }

Een korte uitleg is op zijn plaats. Allereerst de class: ik overerf van OnMethodBoundaryAspect, dat doe ik eigenlijk omdat ik te lui ben om een volledige implementatie van de IOnMethodBoundaryAspect interface te maken. Dit maakt dat ik een aantal overrides kan doen, en ik heb ervoor gekozen om alleen de OnExit te overriden. Ik kan uit de EventArgs halen welke methodenaam het betreft, en hier check ik dus of het een set_* method betreft (Properties get/set accessors worden onderwater vertaald naar set_<propertynaam> en get_<propertynaam> methoden). Als dit zo is check ik of het betreffende object afgeleid is van DomainObject<> (mijn supertype heeft een generic parameter – ik ga er hier even niet op in waarom). Als dit ook het geval is vuur ik de OnPropertyChanged methode af. Hier gebeurt dat d.m.v. reflection, omdat ik niet kan casten naar DomainObject<> (vandaar typenaam “DomainObject`1”) vanwege de generic parameter (dat kan wel eventueel, maar daar wordt het volgens mij niet simpeler van).

Rest nog een ding, en dat is de classes waar het voor geldt (of de gehele assembly, dat kan ook nog) te annoteren met [NotifyingAspect]. Compilen – PostSharp weeft de code erin – en klaar!


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development

Tags: , ,


Reacties (2)

  • Gael Fraiteur schreef:

    Thank you for this blog post, Andre.
    As mentioned by Jaap, there is a sample implementation of INotifyPropertyChanged installed with PostSharp in the Samples directory. This implementation is cleaner: it is more performant at runtime, and does not use reflection at runtime. However, it is more technical and not suitable for an introduction article…

    Keep writing about PostSharp!

    Gael

    Geplaatst op 15 december 2007 om 0:47 Permalink

  • Jaap Taal schreef:

    Op de website van PostSharp staat ook een artikel over DataBinding:
    http://doc.postsharp.org/UserGuide/Samples/PostSharp.Samples.Binding.html

    Geplaatst op 25 november 2007 om 12:57 Permalink