C# – Compile-time checking of property names for Data Bindings

Ever wished you could bind properties to your input controls without using literal strings to identify the property name?

Want the compiler to check the property names are valid at compile time rather than waiting until/if it’s found while testing?

Here is an example of how to determine the name of a property using the property name as compilable code via Lambda’s

(Updated 9th Oct 2009 to support binding paths that extend beyond a first-level property)

The use of datasets / SQL to LINQ or other data persistence layers in .NET make it much easier to use a database to define your entities, i.e. drag and drop your table on and you can then start passing that around as your business object.

If you change the underlying data structures in the database, that’s fine, just re-create the business object in the designer.

This all works nicely and client applications that might be consuming web-services that return these business objects will get a nice compiler error if Person.PhoneNum is changed to Person.PhoneNumber between releases. Everything is hunky-dory until you discover that your data bindings to “PhoneNum” are now incorrect (you know, those hard-coded literal strings that identify an object’s property name to be used via reflection at a later time).

In the above case where Person.PhoneNum is changed to Person.PhoneNumber the application will still compile and the error will not be apparent until runtime. Even then it will not be raised as an exception, only noticeable through a change in behaviour where the text box is empty and does not update its value to the data source (assuming someone notices it).

A traditional binding using a literal string to identify the property:

    textBox2.DataBindings.Add(new Binding("Text", dataSource, "PhoneNum"));

Now you could create an enumeration of available properties, put procedures in place to catch these problems, or rely on your automated tests to identify a problem, but the point of these data layers is to reduce the amount of grunt work involved. Creating enumerations of available properties just defeats this purpose, and really I just want to be told at compile time that it is wrong!

Enter Generics, Lambda Expressions and Expression Trees. These great advents to the C# language provide the means for a great solution that allows you to determine the name of a property from a reference to the property itself with no hard-coded literal strings, e.g. anInstanceOfPerson.PhoneNum would be able to give us “PhoneNum”, and when PhoneNum is changed to PhoneNumber we will get a compile-time error indicating that the property no longer exists.

This is done by passing in a lambda expression to a utility function that pulls apart the expression tree to retrieve the member/method we want to display the name of. The C# code listing below is a complete example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApplication1
{
    public static class Utility
    {
        public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
        {
            if (ExpressionType.MemberAccess == expression.Body.NodeType)
            {
                return (expression.Body as MemberExpression).Member.Name;
            }
            else if (ExpressionType.Call == expression.Body.NodeType)
            {
                return (expression.Body as MethodCallExpression).Method.Name;
            }
            else if (ExpressionType.Convert == expression.Body.NodeType)
            {
                return ((expression.Body as UnaryExpression).Operand as MemberExpression).Member.Name;
            }
            throw new InvalidOperationException("Unsupported NodeType: '"+expression.Body.NodeType.ToString()+"'");
        }
    }

    public class Person
    {
        public string Name;
        public string PhoneNumber;
        public string GetPhoneNumber()
        {
            return PhoneNumber;
        }
    }

    class Program
    {
        public static void Main()
        {
            Console.WriteLine(Utility.GetPropertyName<Person>(p => p.PhoneNumber));
            Console.WriteLine(Utility.GetPropertyName<Person>(p => p.GetPhoneNumber()));
            Console.ReadLine();
         }
    }
}

The above outputs:

If you try with p.PhoneNum instead of p.PhoneNumber you will notice that you get a compile time error now.

The new text box binding example would look like the following, a little extra typing, but far less hassle in the long run:

    textBox2.DataBindings.Add(new Binding("Text", dataSource, Utility.GetPropertyName<Person>(p => p.PhoneNumber)));

Or in VB.NET:

    textBox2.DataBindings.Add(New Binding("Text", dataSource, Utility.GetPropertyName(Of Person)(Function(p) p.PhoneNumber)))

There’s a few good examples of this out there in google land, and hopefully this example will add to this.

—————————————–
Update 9th October 2009:

As pointed out by Nicole’s comment on this thread, GetPropertyName in my example above does not support the use of a binding path that extends beyond a first-level property. Below is an additional method based upon Nicole’s comments and example that retrieves the full binding path (note that I have removed member call support as these don’t make sense in a binding scenario – the code is left commented in case it is useful for another purpose):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApplication1
{
    public static class Utility
    {
        public static string GetPropertyPath<T>(Expression<Func<T, object>> expression)
        {
            // Working outside in e.g. given p.Spouse.Name - the first node will be Name, then Spouse, then p
            IList<string> propertyNames = new List<string>();
            Expression currentNode = expression.Body;
            while (currentNode.NodeType != ExpressionType.Parameter)
            {
                switch (currentNode.NodeType)
                {
                    case ExpressionType.MemberAccess:
                    case ExpressionType.Convert:
                        MemberExpression memberExpression;
                        memberExpression = (currentNode.NodeType == ExpressionType.MemberAccess ? (MemberExpression)currentNode : (MemberExpression)((UnaryExpression)currentNode).Operand);
                        if (memberExpression.Member.MemberType != MemberTypes.Property
                            && memberExpression.Member.MemberType != MemberTypes.Field)
                        {
                            throw new InvalidOperationException("The member '" + memberExpression.Member.Name + "' is a " + memberExpression.Member.MemberType.ToString() + " but a Property or Field is expected.");
                        }
                        propertyNames.Add(memberExpression.Member.Name);
                        currentNode = memberExpression.Expression;
                        break;
                    case ExpressionType.Call:
                        MethodCallExpression methodCallExpression = (MethodCallExpression)currentNode;
                        throw new InvalidOperationException("The member '" + methodCallExpression.Method.Name + "' is a method call but a Property or Field was expected.");

                    // To include method calls, remove the exception and uncomment the following three lines:
                    //propertyNames.Add(methodCallExpression.Method.Name);
                    //currentExpression = methodCallExpression.Object;
                    //break;
                    default:
                        throw new InvalidOperationException("The expression NodeType '" + currentNode.NodeType.ToString() + "' is not supported, expected MemberAccess, Convert, or Call.");
                        break;
                }
            }
            return string.Join(".", propertyNames.Reverse().ToArray());
        }
    }

    public class Person
    {
        public Person Spouse;
        public string Name;
        public string PhoneNumber;
        public string GetPhoneNumber()
        {
            return PhoneNumber;
        }
    }

    class Program
    {
        public static void Main()
        {
            Console.WriteLine(Utility.GetPropertyPath<Person>(p => p.Spouse.Name));
            Console.ReadLine();
         }
    }
}

So with the modified Person class that includes a member Spouse of type Person the above code will now output:

"Spouse.Name"

2 thoughts on “C# – Compile-time checking of property names for Data Bindings”

Leave a Reply