Fabrice's weblog

Tools and Source

News

My .NET Toolbox
An error occured. See the script errors signaled by your web browser.
No tools selected yet
.NET tools by SharpToolbox.com

Read sample chapters or buy LINQ in Action now!
Our LINQ book is also available on AMAZON

.NET jobs

Emplois .NET

Tuneo

ASP.NET Hosting transatlantys

Contact

Me

Others

Selected content

Archives

PropertyOf and INotifyPropertyChanged.PropertyChanged without strings

When coding applications using design patterns such as MVP (Model-View-Presenter) or MVVM (Model-View-ViewModel), you'll find yourself using data binding and the INotifyPropertyChanged interface all over the place. This means referencing properties quite a lot.

For example, when you want to notify that the value of a property has changed, you can write NotifyPropertyChange("SomeProperty") where NotifyPropertyChange is a method that invokes the INotifyPropertyChanged.PropertyChanged event.

The major inconvenience of using a string to reference a property name, as in NotifyPropertyChange("SomeProperty"), is that it's a very brittle approach. If the name of the property changes or the property is removed during some refactoring operation on the source code, then it's easy to forget to update all the "string references".

Luckily, solutions exist to avoid this kind of issues.

infoof => PropertyOf, FieldOf, MethodOf, ConstructorOf

We don't have propertyof or infoof operators in C# (see 1, 2, 3 or 4), but we can use things such as PropertyOf thanks to expression trees.

You can learn more about such an approach on the C# FAQ blog and Patrick Smacchia's blog.

Here is the source code, derived from the above articles, that I use in a utility class:

Snippet

public static MethodInfo MethodOf<T>(Expression<Func<T>> expression)
{
  var callExpr = expression.Body as MethodCallExpression;
  // If the method gets a lambda expression that is not a method call,
// an exception is thrown

  if (callExpr == null)
    throw new ArgumentException("Expression \"" + expression +
 "\" is not a valid method call.");
  return callExpr.Method;
}

public static MethodInfo MethodOf(Expression<Action> expression)
{
  var callExpr = expression.Body as MethodCallExpression;
  // If the method gets a lambda expression that is not a method call,
// an exception is thrown

  if (callExpr == null)
    throw new ArgumentException("Expression \"" + expression +
 "\" is not a valid method call.");
  return callExpr.Method;
}

public static ConstructorInfo ConstructorOf<T>(
Expression<Func<T>> expression)
{
  var newExpr = expression.Body as NewExpression;
  // If the method gets a lambda expression that is not a constructor call,
// an exception is thrown

  if (newExpr == null)
    throw new ArgumentException("Expression \"" + expression +
 "\" is not a valid constructor call.");
  return newExpr.Constructor;
}

public static PropertyInfo PropertyOf<T>(Expression<Func<T>> expression)
{
  var memberExpr = expression.Body as MemberExpression;
  // If the method gets a lambda expression that is not a member access,
  // for example, () => x + y, an exception is thrown
  if (memberExpr == null)
    throw new ArgumentException("Expression \"" + expression +
 "\" is not a valid member expression.");
  var property = memberExpr.Member as PropertyInfo;
  if (property == null)
    throw new ArgumentException("Expression \"" + expression +
 "\" does not reference a property.");
  return property;
}

public static FieldInfo FieldOf<T>(Expression<Func<T>> expression)
{
  var memberExpr = expression.Body as MemberExpression;
  // If the method gets a lambda expression that is not a member access,
  // for example, () => x + y, an exception is thrown
  if (memberExpr == null)
    throw new ArgumentException("Expression \"" + expression +
"\" is not a valid member expression.");
  var field = memberExpr.Member as FieldInfo;
  if (field == null)
    throw new ArgumentException("Expression \"" + expression +
 "\" does not reference a field.");
  return field;
}

INotifyPropertyChanged.PropertyChanged

PropertyOf can be used for invoking PropertyChanged without risking to pass an incorrect string.

Before we see how, let's review all the solutions I use to fire the INotifyPropertyChanged.PropertyChanged event. The following methods come from my base ViewModel (MVVM) class.

First, the most common implementation:

Snippet

protected void NotifyPropertyChanged(String propertyName)
{
  var handler = PropertyChanged;
  if (handler == null)
    return;
  handler(thisnew PropertyChangedEventArgs(propertyName));
}

Sample use: NotifyPropertyChanged("SomeProperty")

This exhibits what we want to avoid: a string reference.

The following version is relatively direct as well, but probably less useful:

Snippet

protected void NotifyPropertyChanged(PropertyInfo property)
{
  var handler = PropertyChanged;
  if (handler == null)
    return;
  handler(thisnew PropertyChangedEventArgs(property.Name));
}

Sample use: NotifyPropertyChanged(property), where property is a PropertyInfo instance you already have at hand.

The following one uses PropertyOf and ensures you'll get a compile-time error if you try to reference a missing or renamed property:

Snippet
protected void NotifyPropertyChanged<T>(Expression<Func<T>> expression)
{
  var handler = PropertyChanged;
  if (handler == null)
    return;
  var propertyName = Helpers.PropertyOf(expression).Name;
  handler(thisnew PropertyChangedEventArgs(propertyName));
}

Sample uses: NotifyPropertyChanged(() => SomeProperty)
                 or NotifyPropertyChanged(() => SomeObject.SomeProperty)

Finally, here is a version that has to be called from a property setter:

Snippet

protected void NotifyPropertyChanged()
{
  var methodName = new StackFrame(1).GetMethod().Name;
  if (!methodName.StartsWith("set_"))
    throw new Exception("This overload of the NotifyPropertyChanged" +
"method must be called from a property setter.");
  NotifyPropertyChanged(methodName.Substring("set_".Length));
}

Sample use:

Snippet

public String SomeProperty
{
  get { return _SomeProperty; }
  set
  {
    if (value == _SomeProperty)
      return;
    _SomeProperty = value;
    NotifyPropertyChanged();
  }
}
The above overload will extract the property name from the current call stack.

Warnings:

  • StackFrame.GetMethod may be considered expensive, depending on the context.
  • This approach won't work if obfuscation has been applied to the binaries.
  • It seems that this won't work either without debug symbols, although I haven't checked.

Another example: Windows Forms data binding

Of course, PropertyOf and its friends are not useful only for PropertyChanged. They can be used in several cases.
For example, here is a standard way of creating a data binding in Windows Forms:

txtCustomerName.DataBindings.Add("Text", Presenter, "CustomerName");

Let's decrypt the above for those of you not familiar with Windows Forms data binding. Here, txtCustomerName is a TextBox. "Text" is the name of the property of this TextBox on which you want to create a binding. Presenter is the "data source" object. "CustomerName" is the name of the property of the data source that you want to bind to the Text property.
The documentation is here.

Of course, what we want to avoid here is to use strings to reference properties. Here is what you can write with PropertyOf:

txtCustomerName.DataBindings.Add(
PropertyOf(() => txtCustomerName.Text).Name,
Presenter,
PropertyOf(() => Presenter.CustomerName).Name);
No strings. In addition, it becomes clear when reading such code where the properties come from. No need to remember that the first parameter must designate a property of txtCustomerName and the third one a property of Presenter.

If you want to simplify your code, you can create a helper method such as the following, which uses PropertyOf internally:

AddBinding(txtCustomerName, () => txtCustomerName.Text,
Presenter, () => Presenter.CustomerName);

I'm sure you'll find more uses for these helpers.

Armed with these tools, you can now go back to writing code, but more reliable code.

Comments

wekempf said:

You warn that StackFrame may be considered expensive (not to mention the issues mentioned by Ryan), but the same thing can be said for the "static reflection" (Expression evaluation). If you want to eliminate the magic strings, yet remain performant in all situations, you should cache the results of the Expression evaluation.

private static readonly PropertyInfo FooProperty = Helper.PropertyInfo((MyClass c) => c.Foo);

private string foo;

public string Foo

{

  get { return this.foo; }

  set

  {

     this.foo = value;

     NotifyPropertyChanged(FooProperty);

  }

}

Quite a bit more work. Without compiler support for "infoof" there's no optimal solution available to us.

# August 30, 2010 4:12 PM

Fabrice Marguerie said:

PostSharp and AOP in general are useful for simple cases, but are not adapted to all cases.

A property setter is not always empty, for example. We may also want to fire PropertyChanged for several properties in a setter.

# August 31, 2010 5:18 AM

Craig Boland said:

The CSLA framework addresses the stackframe issue by adding

System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)

attribute to property getters and setters. I like the PostSharp/AOP approach. Less busy work and room for error.

# August 31, 2010 5:07 PM

PandaWood said:

Thanks Simon, yes the notifypropertyweaver project is almost identical to what we implemented.

Fabrice, you wanted to fire PropertyChanged for several properties... we also cater for this and so does notifypropertyweaver

our custom postsharp:

[Notify("CanSave", "SomeOtherProperty")]

public bool IsDirty { get; set; }

notifypropertyweaver example:

[NotifyProperty(AlsoNotifyFor = new[]{"FullName"})]

public string GivenName { get; set; }

IL weaving may not cater for wanting other code in the setter - which I would argue is generally so undesirable as to be better left out anyway.

But is IL weaving not clearly the better approach in this case?

This being true would seem to destroy the basis of your original blog proposal, but this is not necessarily a bad thing - feel the love ;-)

# September 2, 2010 10:08 PM

Simon said:

@PandaWood

notifypropertyweaver now does not need attributes and derives the dependencies between properties

Your Code

public class Person : INotifyPropertyChanged

{

   public string GivenNames { get; set; }

   public string FamilyName { get; set; }

   public string FullName

   {

       get

       {

           return string.Format("{0} {1}", GivenNames, FamilyName);

       }

   }

   #region Event and event invoker method

}

What gets compiled

public class Person: INotifyPropertyChanged

{

   private string givenNames;

   public string GivenNames

   {

       get { return givenNames; }

       set

       {

           if (value!= givenNames)

           {

               givenNames = value;

               OnPropertyChanged("GivenNames");

               OnPropertyChanged("FullName");

           }

       }

   }

   private string familyName;

   public string FamilyName

   {

       get { return familyName; }

       set {

           if (value != familyName)

           {

               familyName = value;

               OnPropertyChanged("FamilyName");

               OnPropertyChanged("FullName");

           }

       }

   }

   public string FullName

   {

       get

       {

           return string.Format("{0} {1}", GivenNames, FamilyName);

       }

   }

   #region Event and event invoker method

}

# September 22, 2010 8:02 PM

Jean-Francois Chatelain said:

Bonjour Fabrice, je ne sais pas si tu te souviens de moi. J'étais un des québécois sur l'équipe SPOK à la SGCIB en 2008...

J'ai une question pour toi concernant le sujet de ton blog. Est-ce que tu saurais comment récupérer la valeur d'un object d'une expression. Je parle ici, de l'object initial et non la valeur de la propriété dans l'expression. Voici un exemple:

=========================================

DoSomething(() => presenter.MaPropitete)

==========================================

Comment, via reflection, récupérer l'objet presenter?

Merci de ton aide.

Bonne journée

Jean-François Chatelain

# May 31, 2011 9:06 AM

Fabrice Marguerie said:

Salut Jean-Francois,

Ce n'est pas possible. La valeur de presenter n'est pas passée à DoSomething.

Tu peux juste récuperer le type qui déclare la propriété MaPropriete, et donc un type de la hiérarchie de presenter, si ça t'intéresse.

Exemple de code :

void DoSomething(Expression<Func<T>> expression)

{

var memberExpr = expression.Body as MemberExpression;

 if (memberExpr != null)

 {

   var typeDeclaringMember = memberExpr.Member.DeclaringType;

   ...

 }

}

Fabrice

# June 1, 2011 7:23 PM

Jean-Francois Chatelain said:

Merci beaucoup Fabrice

# June 2, 2011 12:58 PM