Wednesday, 7 May 2008

Implementing INotifyPropertyChanged with DynamicProxy2

22:30 and still working on various bits and bobs, so I thought I'd take a well deserved break to tell you about one nice way to implement INotifyPropertyChanged, the one and only interface any class that will be bound in WPF should be implemented.

I used to (circa .net 2) not see a bit issue in this kind of code.

public class MyClass : INotifyPropertyChanged

{

    private string _myValue;

    public event PropertyChangedEventHandler PropertyChanged;

    public string MyValue

    {

        get

        {

            return _myValue;

        }

        set

        {

            _myValue = value;

            RaisePropertyChanged("MyValue");

        }

    }

    protected void RaisePropertyChanged(string propertyName)

    {

        if (PropertyChanged != null)

            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

    }

}

But I've been badly spoiled by automatic properties in C# 3.0, and this all sounds like a lot of noise. How great would it be if we could instead simply have the following.

public class MyDreamClass : INotifyPropertyChanged

{

    public event PropertyChangedEventHandler PropertyChanged;

    public string MyValue { get; set; }

}

Definitly much cleaner.  We're going to use DynamicProxy2, which is part of the Castle framework, and is used amongst other things in nhibernate.

The process is quite simple. You ask a ProxyGenerator to create a type for you, and pass it an interceptor object that will... Intercept any call!

First, let's clear up our class. Because the object generated by DP2 inherits from the original type, it needs to have a virtual modifier on the property. Furthermore, I wanted to add an attribute to specify which properties were to trigger the notification.

public class MyBetterClass : INotifyPropertyChanged

{

    public event PropertyChangedEventHandler PropertyChanged;

    [Notify]

    public virtual string MyValue { get; private set; }

}

Now, let's see how we create the object.

static void Main(string[] args)

{

    var proxy = new ProxyGenerator();

 

    MyBetterClass myClass = proxy.CreateClassProxy<MyBetterClass>(new NotifyPropertyChangedInterceptor());

 

    myClass.PropertyChanged += (src,prop) => Console.WriteLine(prop.PropertyName);

 

    myClass.MyValue = "testValue";

}

Fairly simple so far. Now let's see the meat of the code, the interceptor.

public class NotifyAttribute : Attribute { }

public class NotifyPropertyChangedInterceptor : IInterceptor

{

    public void Intercept(IInvocation invocation)

    {

        // let the original call go through first, so we can notify *after*

        invocation.Proceed();

 

        if (invocation.Method.Name.StartsWith("set_"))

        {

            string propertyName = invocation.Method.Name.Substring(4);

            var pi = invocation.TargetType.GetProperty(propertyName);

 

            // check that we have the attribute defined

            if (Attribute.GetCustomAttribute(pi, typeof(NotifyAttribute)) == null)

                return;

 

            // get the field storing the delegate list that are stored by the event.

            FieldInfo info = invocation.TargetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)

                .Where(f => f.FieldType == typeof(PropertyChangedEventHandler))

                .FirstOrDefault();

 

            if (info != null)

            {

                // get the value of the field

                PropertyChangedEventHandler evHandler = info.GetValue(invocation.InvocationTarget) as PropertyChangedEventHandler;

                // invoke the delegate if it's not null (aka empty)

                if (evHandler != null)

                    evHandler.Invoke(invocation.TargetType, new PropertyChangedEventArgs(propertyName));

            }

        }

    }

}

And that's it. Get the field in which the event is stored and invoke it whenever a property has been modified.

14 comments:

Graeme Foster said...

Hi Seb,

This is neat... I was getting sick of not being able to use the new property syntax as-well!

You can also do this with a RealProxy object without Windsor.

The main differences are that you have to mark the ViewModel class as MarshallByRefObject but you don't need to mark the accessors as virtual.

Then you create a new realproxy subclass which overrides the Invoke method to look for "set_" just as your code does.

The downside is its harder to debug the viewmodel because the app only sees the transparent proxy. The plus side is if you don't need the additional dependency on Windsor if you didn't already have it!

Cheers,
Graeme

Sebastien Lambla said...

Hey Graeme,

you're absolutely right, you can go the TransparentProxy / RealProxy way, but there are two big issues with that.

First you change your inheritence hierarchy to go with MBRef. This has issues in terms of design, and more importantly with lifetime management: as soon as you create the RP/TP you have to deal with MBR lifetime, which means no more garbage collection goodness.

Second one is a performance issue, the TP/RP couple is expensive to create and use. And the model of dealing with channels is just plain scary and awful.

That said, DP2 is quite independent from windsor so you don't need windsor to still do interception :)

Graeme Foster said...

Hi Seb,

Completely agree with the point about design issues and changing to inherit from MRB...

Can you clarrify what you mean by "And the model of dealing with channels is just plain scary and awful..."

The code I wrote just used the RP's Invoke method to invoke on the Real Object. The RealObject was just a property on the RealProxy...

Or do you mean the mechanism .Net uses internally to marshall that message through to the real object?

Cheers,

Grae

Graeme Foster said...

As for performance - just done some stats and Dynamic Proxy is about 7-8 times faster than using RealProxy.

Neat. I'm a convert!!

Sebastien Lambla said...

Glad the performance test confirms it :)

Yes I was referring to the way .net marshals messages and the complexity of the API.

I remember in the .net 1.0 days, I implemented a plug-in system not unsimilar to the System.AddIn you can find in .net 3.5, and having to deal with injecting yourself in the pipeline that gets created when marshalling across appdomains is plain scary.

I think you only have to manage it when using CBOs or MBR beyond your appdomain. But it's not a model that makes you feel warm inside :)

Andreas Hallberg said...

Nice work! I'm doing something very similar in EasyProp.

Have you gotten it to work when databinding to WPF controls? I'm failing in the same fashion as this person.. Any ideas? If there is a solution it seems like it involves modifying DynamicProxy2.

Cheers,

Andreas

Sebastien Lambla said...

I'll investigate this weekend, but top of my head you probably want to have a look at the TypeDescriptor infrastructure, as that's what the binding engine uses.

The interface way would not work, as anything that would set the code internally (aka the code of the class) wouldn't trigger the proxy setter, as the proxy setter would only be called on the interface setter. So your notifypropertychange wouldnt get triggered.

Svenne said...

Frank Quednau figured out the WPF-binding problem: http://realfiction.net/?q=node/160

Sebastien Lambla said...

Not entirely sure I like the interface to be magically implemented by the runtime as it would have an impact to anything that could inherit from it. I'd rather be explicit about the interface and implicit about the implementation than leave too much magic.

Anonymous said...

Great work! I played a bit with your code and find a small bug for WPF binding to register the property changed event

After I “upgraded” DynamicProxy2 the AmbiguousMatchException went away but in
your Intercept function when you Invoke the PropertyChangedEventHandler you use
invocation.TargetType
as the object and not
invocation.InvocationTarget

After changing this, it works now 100%.

Romain Verdier said...

Another option is to use a post compiler like PostSharp or AspectDNG. It allows you to weave your aspect in any class, even sealed ones, and even using multicasting. Performances are really correct, and you don't have to look for methods which begin with "set_" :)

andres said...

I make a semi-generic solution without proxies thats implements INotifyPropertyChanged, INotifyPropertyChanging, IEditableObject and other level of manual discard changes. This can be used in this way:

public class AnyBussinesObjectUIRepresentation : Entities.AnyBussinesObjectBase, IEditableUIObject
{
public override bool Active
{
get { return editableData.GetData&gr;bool>("Active"); }
set { editableData.SetData("Active", value); }
}

public override string Description
{
get { return editableData.GetData&gr;string>("Description"); }
set { editableData.SetData("Description", value); }
}

public override string Code
{
get { return editableData.GetData&gr;string>("Code"); }
set { editableData.SetData("Code", value); }
}

public override Guid Id
{
get { return editableData.GetData&gr;Guid>("Id"); }
set { editableData.SetData("Id", value); }
}

public override string Name
{
get { return editableData.GetData&gr;string>("Name"); }
set { editableData.SetData("Name", value); }
}


#region IEditableUIObject Implementation

private IEditableUIObjectSupporter editableData = new EditableUIObjectSupporter();

public event PropertyChangingEventHandler PropertyChanging
{
add { editableData.PropertyChanging += value; }
remove { editableData.PropertyChanging -= value; }
}

public event PropertyChangedEventHandler PropertyChanged
{
add { editableData.PropertyChanged += value; }
remove { editableData.PropertyChanged -= value; }
}

[IgnoreDataMember]
public bool DiscartableChangesControl
{
get { return editableData.DiscartableChangesControl; }
set { editableData.DiscartableChangesControl = value; }
}

public bool IsDirty
{
get { return editableData.IsDirty; }
}

public void DiscardChanges()
{
editableData.DiscardChanges();
}

public void AcceptChanges()
{
editableData.AcceptChanges();
}

public ValueChangedDescriptorCollection GetChanges()
{
return editableData.GetChanges();
}

void IEditableObject.BeginEdit()
{
editableData.BeginEdit();
}

void IEditableObject.CancelEdit()
{
editableData.CancelEdit();
}

void IEditableObject.EndEdit()
{
editableData.EndEdit();
}

#endregion
}

I don't paste the base code because there are 300 lines but you can see it in http://code.google.com/p/facturanet/source/browse/#svn/trunk/Facturanet.Core/UI

Maybe the Hashtables are not so efficients, but I think that it will be enough for my project.

Thanks for the inspiration.

Jmix90 said...

Hello,

Very interesting post..

I did the same here : http://blog.lexique-du-net.com/index.php?post/2010/03/11/MVVM-Creating-ViewModel-%3A-create-dynamic-proxies-with-Castle

JobLot said...

this does not work in Silverlight. Call to invocation.Proceed() fails with exception

"Attempt to access the method failed: System.Reflection.Emit.DynamicMethod..ctor(System.String, System.Type, System.Type[], System.Reflection.Module, Boolean)"

how can I use this in Silverlight application. thanks

Post a Comment