Archive

Be seated safely before reading on. This kicks ass.

I have already written enough abstract words in previous posts; let's now introduce PostSharp 2.0 on a real example: the implementation of  NotifyPropertyChanged, one of the most frequently used patterns for all adepts of MVC designs.

It's not just about implementing the INotifyPropertyChanged interface; it's also about modifying every property setter. It's deadly simple and deadly boring. And gets you more than one step away from the idea you have of aesthetical code.

Enough words. See the implementation using PostSharp 2.0. There's a lot of new concepts out there, so let's start easily:

[Serializable]
[IntroduceInterface( typeof(INotifyPropertyChanged))]
public sealed class NotifyPropertyChangedAttribute : 
   InstanceLevelAspect, INotifyPropertyChanged
{
    public void OnPropertyChanged( string propertyName )
    {
        if ( this.PropertyChanged != null )
        {
            this.PropertyChanged( this.Instance, 
               new PropertyChangedEventArgs( propertyName ) );
        }
    }

    [IntroduceMember]
    public event PropertyChangedEventHandler PropertyChanged;

    [OnLocationSetHandler, 
     MulticastSelector( Targets = MulticastTargets.Property )]
    public void OnPropertySet( LocationInterceptionArgs args )
    {
        if ( args.Value == args.GetCurrentValue() ) return;

        this.OnPropertyChanged( args.Location.PropertyInfo.Name );
    }
}

The aspect is used this way:

[NotifyPropertyChanged]
public class TestBaseClass
{
    public string PropertyA { get; set; }
}

Some words of explanation.

First, look at introductions: IntroduceInterface tells that the interface INotifyPropertyChanged must be introduced into the target type. And where is the interface implemented? Well, by the aspect itself! Note that PropertyChanged is itself introduced to the target type as a public member thanks to the IntroduceMember custom attribute (without this attribute, the interface would be implemented explicitely).

The aspect actually becomes an extension of the class; aspect instances have the same lifetime as objects of the class to which the aspect is applied... just because the aspect inherits from InstanceLevelAspect.

Got it? So let's continue to the OnPropertySet method. It is marked by custom attribute OnLocationSetHandler: it makes this method a handler that intercepts all calls to the setter of all target properties. And which are target properties? This is told by custom attribute MulticastSelector: all properties in this case, but we could filter on name, visibility and all features you are used to. We could have used the same handler to handle access to properties (it would have turned the field to a property as a side effect).

Did you get it? You can now catch accesses to properties or fields using the same aspects. Same semantics, same aspect. Simple, powerful.

Now look at the body of method OnPropertySet and see how easy it is to read the current value of the property.

The code above works on an isolated class, but a class is rarely isolated, right? More of the time, it derives from an ancestor and have descendants. What if the ancestor already implements interface INotifyPropertyChanged? The code above would trigger a build time error. But we can improve it. What do we want, by the way? Well, if the class above already implements INotifyPropertyChanged, it must also have implemented a protected method OnPropertyChanged(string), and we have to invoke this method. If not, we define this method ourselves. In both cases, we need invoke this method from all property setters.

Let's turn it into code:

[Serializable]
[IntroduceInterface( typeof(INotifyPropertyChanged), 
                     OverrideAction = InterfaceOverrideAction.Ignore )]
[MulticastAttributeUsage( MulticastTargets.Class, 
                          Inheritance = MulticastInheritance.Strict )]
public sealed class NotifyPropertyChangedAttribute : 
   InstanceLevelAspect, INotifyPropertyChanged
{
   
    [ImportMember( "OnPropertyChanged", IsRequired = false )] 
    public Action<string> BaseOnPropertyChanged;

    [IntroduceMember( Visibility = Visibility.Family, 
                      IsVirtual = true, 
                      OverrideAction = MemberOverrideAction.Ignore )]
    public void OnPropertyChanged( string propertyName )
    {
        if ( this.PropertyChanged != null )
        {
            this.PropertyChanged( this.Instance, 
               new PropertyChangedEventArgs( propertyName ) );
        }
    }

    [IntroduceMember( OverrideAction = MemberOverrideAction.Ignore )]
    public event PropertyChangedEventHandler PropertyChanged;

    [OnLocationSetHandler, 
     MulticastSelector( Targets = MulticastTargets.Property )]
    public void OnPropertySet( LocationInterceptionArgs args )
    {
        if ( args.Value == args.GetCurrentValue() ) return;

        if ( this.BaseOnPropertyChanged != null )
        {
            this.BaseOnPropertyChanged( args.Location.PropertyInfo.Name );
        }
        else
        {
            this.OnPropertyChanged( args.Location.PropertyInfo.Name );
        }
    }
}

This time, the code is complete. No pedagogical simplification. Look at custom attributes IntroduceInterface and IntroduceMember: I have added an OverrideAction; it tells that interface and member introductions will be silently ignored if already implemented above.

Now look at field BasePropertyChanged: its type is Action<string> (a delegate with signature (string):void) and is annotated with custom attribute ImportMember. At runtime, this field with be bound to method OnPropertyChanged of the base type. If there is no such method in the base type, the field will simply be null. So, in method OnPropertySet, we can now choose: if there was already a method OnPropertyChanged, we invoke the existing one. Otherwise, we invoke the one we are introducing.

Thanks to ImportMember, we know how to extend a class that already implement the pattern. But how to make our own implementation extensible by derived classes? We have to introduce the method OnPropertyChanged and make it virtual. It's done, again, by custom attribute IntroduceMember.

That's all. You can now use the code on class hierarchies, like here:

[NotifyPropertyChanged]
public class TestBaseClass
{
    public string PropertyA { get; set; }
}
public class TestDerivedClass : TestBaseClass
{
    public int PropertyB { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        TestDerivedClass c = new TestDerivedClass();
        Post.Cast<TestDerivedClass, INotifyPropertyChanged>( c ).PropertyChanged += OnPropertyChanged;
        c.PropertyA = "Hello";
        c.PropertyB = 5;
    }

    private static void OnPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
        Console.WriteLine("Property changed: {0}", e.PropertyName);
    }
}

With PostSharp 1.5, you could do implement easy aspects easily. With PostSharp 2.0, you can implement more complex design patterns, and it's still easy.

Happy PostSharping!

-gael

P.S. Kicking?

Comments (43) -

Markus Wagner
Markus Wagner
9/14/2009 9:20:24 PM #

Amazing!

RobertMcCarter
RobertMcCarter
9/14/2009 10:27:03 PM #

This is absolutely amazing!
So, when can we get our hands on it!?!

PS.  I loved "No pedagogical simplification".  :-)

RobertMcCarter
RobertMcCarter
9/14/2009 10:29:23 PM #

BTW, I've wanted this capability for AGES!  Previously it seemed you had to write a low-level PostSharp plug-in to get this kind of functionality.  So the fact that you can now add methods, events, properties, etc to a class this easily is excellent.  True AOP for .NET.

Do you have a sense of the pricing for v2?  How much will this goodness cost?  :-)

Alex Yakunin
Alex Yakunin
9/15/2009 8:26:06 AM #

New code looks really beautiful ;)

Gael Fraiteur
Gael Fraiteur
9/15/2009 9:00:15 AM #

For end users, PostSharp Professional Edition will be priced and licensed as Resharper.

Hermann
Hermann
9/15/2009 9:23:33 AM #

Looks very interesting, I am just worried about performance. Will all this be turned into code during compilation only or is it more like Loas that still does stuff during runtime.

Gael Fraiteur
Gael Fraiteur
9/15/2009 9:34:43 AM #

Generally speaking, runtime performance has been greatly improved in PostSharp 2.0, because you now pay for what you consume. Also, PostSharp 2.0 don't use degates to perform interceptions any more, and don't use extensive value boxing to encapsulate parameters.

However, this precise case uses reflection at runtime to get the property name, and this has a performance cost. I'll work on a solution to avoid this.

sean kearon
sean kearon
9/15/2009 10:21:32 AM #

Gael, that really is a whole load easier than the approach in v1 & v1.5.  Can't wait to see some more!

Shawn Wildermuth
Shawn Wildermuth
9/20/2009 10:32:45 AM #

I have been looking at some of these aspect scenarios but have been hamstrung by Silverlight's lightweight .NET Framework.  Will this work with Silverlight or just the full .NET Framework?

Gael Fraiteur
Gael Fraiteur
9/20/2009 12:09:00 PM #

All of this will work with Silverlight.

Andrew Matthews
Andrew Matthews
9/25/2009 3:39:56 AM #

Hi Gael,

Have you fixed the language again? This looks like a nice way to provide a substitute for multiple inheritance. It is reminiscent of Alexandrescu's Policy Based Programming...

Andrew

RobertMcCarter
RobertMcCarter
10/13/2009 5:53:55 PM #

Can the same aspect know when a property is being changed from within the constructor?  I'd like the aspect to avoid raising the property changed event if the property is set within the constructor.

Yoooder
Yoooder
10/14/2009 12:36:21 AM #

The constructor shouldn't be a concern; the object is still in the process of being created.  It's not possible to add handlers to the events until after the constructor is complete.

McNaught
McNaught
10/14/2009 9:08:57 AM #

NotifyPropertyChanged shouldn't fire until _after_ the property has actually changed. Is this supported?

Gael Fraiteur
Gael Fraiteur
10/14/2009 11:03:55 AM #

@RobertMcCarter RE: Constructors
No, you can't detect where your advice/handler is invoked from unless you use a StackFrame.

Gael Fraiteur
Gael Fraiteur
10/14/2009 11:05:52 AM #

I think there is a mistake in the code sample I gave. The property is actually set when you invoke base.OnSetValue(...). You should invoke that before the notification.

RobertMcCarter
RobertMcCarter
10/14/2009 4:08:00 PM #

I was thinking that you could introduce a boolean, and if you could within the same aspect capture the method entry/exit on the constructor then on entry you could set the boolean to true and on exit false.  Then the property changed code within the aspect could simply test the boolean.

RobertMcCarter
RobertMcCarter
10/14/2009 4:29:55 PM #

That's a good point - my existing aspect has a very complex raise-property implementation with locking (for thread safety) and automatic cascading property changed events (for example if BirthDate changes then the read-only Age property will also "automatically" raise a property changed event).

However, with this new more powerful approach almost all that work (save for a single lock) can be avoided if nobody is listening to the event.

Thanks!

Martijn Muurman
Martijn Muurman
10/20/2009 9:45:13 PM #

OnLocationSetHandler should be OnLocationSetValueAdvice right? Like in the samples.

Martijn Muurman
Martijn Muurman
10/20/2009 11:12:48 PM #

Using the property changed sample as a basis for an aspect I ran into the problem
it does not work yet:

The following code is from the test Program
            c.PropertyA = "Hello";
            //this should write "Value:Hello"..instead we see "Value:"
            Console.WriteLine("Value: {0}",c.PropertyA);
            c.PropertyB = 5;

Addint the following line to OnPropertySet in the attribute seems to fix this problem.

            args.ProceedSetValue();

Is this correct? Is there any documentation yet for these classes? It seems a real nice improvement from 1.5 :)

Gael Fraiteur
Gael Fraiteur
10/21/2009 9:14:00 AM #

Yes, Martijn, you are twice right. Naming was inlined with official AOP terms before release, and I forgot to call base.OnPropertySetValue() in the sample.

Martijn Muurman
Martijn Muurman
10/21/2009 10:47:00 AM #

base.OnPropertySetValue() does not work (not in the base-class)

Did you mean args.ProceedSetValue() ?

Gael Fraiteur
Gael Fraiteur
10/21/2009 11:19:54 AM #

Yes, you are rigth a third time. Seems I need some rest :'(

Lemonhead
Lemonhead
10/22/2009 2:40:53 AM #

The minute you put your class that is tagged with [NotifyPropertyChanged] in a different assembly from the actual definition of NotifyPropertyChangedAttribute, this stops working.

??

That's a serious limitation.  It means I can't just declare the NotifyPropertyChangedAttribute class once in a shared library, then use it throughout my different projects.

Gael Fraiteur
Gael Fraiteur
10/22/2009 8:55:57 AM #

The bug has been solved. You have to download and install a new version.

Sadegh Alavi
Sadegh Alavi
11/21/2009 2:52:12 PM #

This approach does not work properly when we structured project and define inheritance hierarchy.
For instance, when you define a derived class in second assembly from a derived class which inherited from a base class in addition base class tagged with [NotifyPropertyChanged] and both of them are in external assembly (first assembly) , you could not be able to run program or the result not working properly and properties which are defined in second assembly does not work and notified.
I defined this structure with PostSharp 1.5 and tradition inplementation of NotifyPropertyChanged by PostSharp in solution and it works great.
Is there a problem in this structure or lack of support by PostSharp 2.0 ?


Solution Schema:

Assembly 1
  -BaseClass [NotifyPropertyChanged]
     -P1 (Work)
     -P2 (Work)
  -DerivedLevel1 : BaseClass
     -DP1 (Work)

Assembly 1 : Assembly 2
  -DerivedLevel2 : DerivedLevel1
     -D2P1 (Does not Work!)

Cody Skidmore
Cody Skidmore
1/13/2010 10:16:04 PM #

I copied the example code as-is and tried compiling it.  It throws a compiler error saying 'MulticastSelector' not found.

Where does MulticastSelector live?

Gael Fraiteur
Gael Fraiteur
1/14/2010 9:37:14 AM #

There has been some renamings since this blog has been posted.
It is now named MulticastPointcut, OnLocationSetValueAdvice.

no9
no9
2/25/2010 1:15:25 PM #

Hello.

I cant get this to work ... the problem is when i change some value on class that implements [NotifyPropertyChanged] the value is turned right back to what it was. What am i missing here?

    [Serializable]
    [IntroduceInterface(typeof(INotifyPropertyChanged),
                         OverrideAction = InterfaceOverrideAction.Ignore)]
    [MulticastAttributeUsage(MulticastTargets.Class,
                              Inheritance = MulticastInheritance.Strict)]
    public sealed class NotifyPropertyChangedAttribute :
       InstanceLevelAspect, INotifyPropertyChanged
    {

        [ImportMember("OnPropertyChanged", IsRequired = false)]
        public Action<string> BaseOnPropertyChanged;

        [IntroduceMember(Visibility = Visibility.Family,
                          IsVirtual = true,
                          OverrideAction = MemberOverrideAction.Ignore)]
        public void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this.Instance,
                   new PropertyChangedEventArgs(propertyName));
            }
        }

        [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]
        public event PropertyChangedEventHandler PropertyChanged;

        [OnLocationSetValueAdvice,
        MulticastPointcut(Targets = MulticastTargets.Property)]
        public void OnPropertySet(LocationInterceptionArgs args)
        {
            if (args.Value == args.GetCurrentValue()) return;

            if (this.BaseOnPropertyChanged != null)
            {
                this.BaseOnPropertyChanged(args.Location.PropertyInfo.Name);
            }
            else
            {
                this.OnPropertyChanged(args.Location.PropertyInfo.Name);
            }
        }
    }

no9
no9
2/26/2010 11:06:28 AM #

i dont know if im kicking in the right direction ...

this.PropertyChanged(this.Instance,new PropertyChangedEventArgs(propertyName));

inspeting this.Instance is the correct object, but its values are unchanged. Why wont the actual data change?

Gael Fraiteur
Gael Fraiteur
2/26/2010 11:09:35 AM #

You're missing a call to args.ProceedSetValue.

no9
no9
2/26/2010 11:26:47 AM #

ok ... and where should it be called from?

no9
no9
2/26/2010 11:29:43 AM #

nevermind .. i see now post from Martjin.
Sorry guys i didnt see it first time i was reading the comments.

However ... it would make sense that example is updated / fixed.

Thanx again

Gael Fraiteur
Gael Fraiteur
2/26/2010 11:28:01 AM #

After this line:

if (args.Value == args.GetCurrentValue()) return;

You call the setter if the value has changed, then do your custom stuff.

no9
no9
3/1/2010 8:00:52 AM #

Than you Gael !

I would like to ask one more question....

It is awesome that i can implement [NotifyPropertyChanged] on class level... but ...
since my classes are somehow complex i wonder if there is a way to register the events on class level.
My "newStyle" object has 30+ properties that i need to handle ... and writting each is somehow not efficient. Is it possible to do this only on "newStyle" class? ... I hope i make sense !

"Post.Cast<Stroke, INotifyPropertyChanged>(newStyle.Stroke).PropertyChanged += OnPropertyChanged; ... ... ..."

no9
no9
3/1/2010 8:02:07 AM #

I forgot to ask what i intended ... is all of this also possible using PostSharp 1.5 ?

Gael Fraiteur
Gael Fraiteur
3/1/2010 8:41:01 AM #

I guess you meant: " i wonder if there is a way to register the events on PROPERTY level"? No, there is nothing like a property-level event.

Some designs define a (?<propertyName>.+)Changed event for every property (for instance WPF). But this requires to introduce an event whose name is (from the point of view of the aspect developer) dynamic, which is not supported by PostSharp 2.0.

As for PostSharp 1.5, it was only possible to implement a part of the aspect: introducing the interface and advising the property. But it was not possible to introduce/override the OnPropertyChanged method. And it required much more lines of code and complexity.

no9
no9
3/1/2010 1:17:10 PM #

hmmm ... so if i want my classes clean (without implementing INotifyPropertyChange and creating onChange events directly on them) i would need to use version 2.0?
Does 1.5 support instance level attributes?

thanx

no9
no9
3/1/2010 1:32:18 PM #

or let me put it this way .. i cannot insert a method to a class with version 1.5 (in my case OnPropertyChanged method) ... hope im not too much of a pain, but it would really save me time searching.

Gael Fraiteur
Gael Fraiteur
3/1/2010 2:16:14 PM #

Exactly. You could introduce private interface implementations with PostSharp 1.5, but not public/protected methods.

Garth Kidd
Garth Kidd
3/19/2010 3:25:38 AM #

Is it possible to make imported members visible at compile-time, not just at run-time?

When will you update your examples to reflect the current published binaries?

When will you publish documentation for the new version?

I've applied corrections from the comments to the published code, and verified that instance is INotifyPropertyChanged. I can add an event handler to instance.PropertyChanged if I cast instance to dynamic. The handler is called, and the value is set. Excellent.

What I can't do is get a strongly typed variable pointing to the instance and add a handler like this:

var instance = new AspectedClass();
instance.PropertyChanged += (sender, args) => this.GotNotification(args.PropertyName);

Instead, I need to use dynamic and be more explicit about the lambda:

dynamic instance = new AspectedClass();
p.PropertyChanged += new PropertyChangedEventHandler((sender, args) => this.GotNotification(args.PropertyName));

I'm not sure my team are going to be comfortable with PostSharp and casting to dynamic to add an event handler. I'm sure they'll gripe about the extra typing when consuming the classes I've created with PostSharp. “Oh sure, Garth,” I'd say in their position, “PostSharp is saving you a lot of typing, mate… but it's making our life harder.”

I'm also going to have trouble persuading my team to use PostSharp if there's no documentation for PostSharp 2 and the only public code examples for PostSharp 2 use old names from the beta and force potential users to wade through comments just to get the arguments to compile.

Please let me know if it's possible to make the imported members available at compile-time to this project and other projects in the same solution, and when you plan to publish updated examples and documentation.

Gael Fraiteur
Gael Fraiteur
3/19/2010 6:35:20 AM #

Hi Garth,

It is not possible to make the members visible at compile time, due to the technology used by PostSharp. When an interface is introduced, you can use the special Post.Cast<SourceType,TargetType>(SourceType o), which is fully type-safe because checked at post-compile time:

Post.Cast<AspectedClass,INotifyPropertyClass>(instance).PropertyChanged += ...

Imported members are available to other projects of the solution... at least to the compiler. The problem is that Intellisense/Resharper does not see it this way (they look at source code).

I agree with you that PostSharp 2.0 is hard to sell currently -- yet, the release is still labeled a CTP.

The samples installed with PostSharp are up-to-date. You'll find the NotifyPropertyChanged example, as well as other ones.

But the documentation does not include 2.0 stuff yet.

Now that the company is launched and my colleague has booted, I'll have more time for the product and you'll see more progress than previously.

Bottom line: it's not the right time to persuade your team; better wait for a release candidate, which may come in May.

-gael

Toby
Toby
5/12/2010 8:55:41 PM #

I sure wish the community edition supported the features needed for the NotifyPropertyChanged attribute.  Thanks.

Comments are closed