Tuesday, September 14, 2010

Getting the Property Name From a Property Expression

Here’s a common problem. Say we have a function that returns the name of a property from a property expression:

public static string GetPropertyNameFromPropertyExpression<T>(Expression<Func<T, object>> propertyExpression)

We might use it like this:

var propertyName = GetPropertyNameFormPropertyExpression<Grandparent>(g => g.Parent.Child.Name);
Console.WriteLine(propertyName);

Which should output:

Parent.Child.Name

The problem splits itself nicely into three parts:

  1. Get each chained property name.
  2. Intersperse with some separator, in our case “.”
  3. Concatenate.

We’ll do the hardest part first, recursively walking down the expression, yielding PropertyInfos as we go:

public static IEnumerable<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> propertyExpression)
{
    return GetProperties(propertyExpression.Body);
}

private static IEnumerable<PropertyInfo> GetProperties(Expression expression)
{
    var memberExpression = expression as MemberExpression;
    if (memberExpression == null) yield break;

    var property = memberExpression.Member as PropertyInfo;
    if (property == null)
    {
        throw new SutekiCommonException("Expression is not a property accessor");
    }
    foreach (var propertyInfo in GetProperties(memberExpression.Expression))
    {
        yield return propertyInfo;
    } 
    yield return property;
}

This will return an list of properties for ‘Parent’, ‘Child’ then ‘Name’. It uses the classic recursion pattern of a guard clause (testing for a null MemberExpression) followed by the recursive call. It also shows that you can use the ‘yield return’ syntax recursively. It’s a shame there’s not a ‘yield many’ statement, then we wouldn’t have to do the foreach loop and trigger the enumeration.

We can get the names of the properties by selecting the PropertyInfo.Name:

GetProperties<Grandparent>(g => g.Parent.Child.Name).Select(property => property.Name)

Next we need a function to intersperse our “.”. We can write this as a very generic Intersperse:

public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> items, T separator)
{
    var first = true;
    foreach (var item in items)
    {
        if (first) first = false;
        else
        {
            yield return separator;
        }
        yield return item;
    }
}

Last of all we need a 'Concat’ function to concatenate the strings together:

public static string Concat(this IEnumerable<string> items)
{
    return items.Aggregate("", (agg, item) => agg + item);
}

Now we can fill in the body of our GetPropertyNameFromPropertyExpression function:

public static string GetPropertyNameFromPropertyExpression<T>(Expression<Func<T, object>> propertyExpression)
{
    return GetProperties<Grandparent>(g => g.Parent.Child.Name)
        .Select(property => property.Name)
        .Intersperse(".")
        .Concat();
}

I’m becoming a big fan of a more functional style of programming. Granted it’s not as efficient as the imperative version, but I really like the elegance. Anyone who’s looked at Haskell will recognise the ‘Intersperse’ and ‘Concat’ functions, although my crappy C# versions are rather blunt instruments compared with the beauty of Haskell.

7 comments:

  1. Anonymous10:24 am

    Nice article, I've got similar code to remove 'magic' strings and make code refactor friendly.

    I do make use of .NET 4.0's new String.Join IEnumerable overload, not because of potential string concat performance but because it feels more in the spirit of .NET:

    return String.Join(".", GetProperties(propertyExpression).Select(x=>x.Name))

    ReplyDelete
  2. Nice, I didn't even know of String.Join, thanks!

    ReplyDelete
  3. Anonymous9:49 am

    Cool. But what about when i need select property Name from collection of childs.
    class Parent{
    public IEnumerable Childs;
    }

    class Child{
    public string Name;
    }

    ReplyDelete
  4. Hi Anonymous,

    I guess it really depends what you want to use the string for. What does the representation of your collection need to look like. For example, my motivation for doing this work initially was so that I could provide a string to MVC's default binder. If I wanted to represent a collection, I would have do it like this: Parent.Child[0].ChildProperty, but your requirement might be different.

    ReplyDelete
  5. Anonymous12:42 pm

    Hi Mike,
    My motivation to use it in Entity Framework with Include method and i need to get string like "Childs.Name". When I looking for solution first idea was to implement something like you sample. But child collections is problem for me because I do not know which expression use. For this purpose I implemented following interface:
    LoadOptions options = new LoadOptions();
    options.AddCollection(parent => parent.Childs).Add( child => child.Name);
    But i still want to use single method with single lambda. Maybe you has any advices?

    ReplyDelete
  6. Hi Mike,

    Thanks for a great post, this has just made it in to my current project.

    A came across an edge case as my class has some nullable dates which cause a boxing operation when the expression is validated. So I changed the the expression signature from Expression> to Expression> which avoids the boxing operation.

    See here for more details http://stackoverflow.com/questions/3567857/why-are-some-object-properties-unaryexpression-and-others-memberexpression

    ReplyDelete

Note: only a member of this blog may post a comment.