Friday, December 17, 2010

Jounce Part 8: Raising Property Changed

This will be a shorter bit for an interesting topic. I've noticed a few posts and discussion around raising the property changed notification. This continues to be the sore spot for MVVM. It creates quite a bit of ceremony for what is a fairly common piece of functionality that shouldn't be so hard to create.

Jounce supports all of the common ways of doing this, but there is a third that I learned from a colleague of mine (Keith Rome) that I added and think bears some attention.

The most common approach is to use the "magic string" technique (which many developers automatically cringe at) and do something like this:

public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged("FirstName");
    }
}

The problem there of course is that it is too easy to type the property wrong. Coders who typically "cut and paste" might forget to update this and then they'll find themselves raising the wrong property.

The next iteration types the property a bit and gets rid of magic strings. It does this by allowing a lambda expression to be passed in. The expression tree is parsed and the property name is extracted. Some extra validation can even make sure it's inside a property - but there is still no guarantee you aren't raising the wrong property, you just know you are raising some property.

public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged(()=>FirstName);
    }
}

So the third method is a little more safe. Not only does it avoid magic strings, but I can get out of the ceremony of passing a weird lambda expression, and I can guarantee it will raise the notification for the correct property. Before I talk about how this works, let me show you the code:

public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged();
    }
}

This will throw an exception if it's not in a setter. And it will always capture the current property. I still have to make a call, but no magic strings and we've reduced the ceremony somewhat. How does it work?

The trick is the context of execution. All of these methods have a little overhead (the string method actually has the best performance) but in most applications if the performance hit is in your setters, you've probably got worse issues farther up the stack.

Speaking of the stack, that's exactly what we use. We simply walk the stack frames up from the setter to find the name of the setter method itself. The convention for properties is set_propName so we look up where we are at and grab the propName.

Here's the code:

public virtual void RaisePropertyChanged()
{
    var frames = new System.Diagnostics.StackTrace();
    for (var i = 0; i < frames.FrameCount; i++)
    {
        var frame = frames.GetFrame(i).GetMethod() as MethodInfo;
        if (frame != null)
            if (frame.IsSpecialName && frame.Name.StartsWith("set_"))
            {
                RaisePropertyChanged(frame.Name.Substring(4));
                return;
            }
    }
    throw new InvalidOperationException("NotifyPropertyChanged() can only by invoked within a property setter.");
}

Notice once we extract the property name, we just pass it along to the method that takes a happy string.

There you have it - another way to do the deed with a little more overhead but a lot less ceremony.

Addendum

Thanks to everyone for the excellent responses. Apparently this is a method that has been tried in the past. As Sergey Barskiy pointed out, the JITter will likely inline the method and break the stack frame. Scott Bussinger offered this link as well: The Story of a Wicked Little Bug. What's interesting is that you can mark the method (and I have) to avoid inlining, and it seems the consensus is that this works except on 64-bit systems. Currently, there is not a 64-bit Silverlight runtime. However, with future releases we'll have to revisit and determine whether or not this remains an issue. Ideally, I'd love to be able to do this:

[NotifyPropertyChanged]
public string MyProperty { get; set; }

And be done with it.

Jeremy Likness