How to avoid passing property names as strings using C# 3.0 Expression Trees

Referencing property names via strings is evil. Consider this simplistic example:

private int _myProperty;

public int MyProperty
{
    get { return _myProperty; }
    set {
        _myProperty = value;
        NotifyPropertyChanged (this, "MyProperty")
    }
}

// Somewhere in a different project or file...
private void NotifyPropertyChanged (object sender, string propertyName)
{
    if (propertyName == "MyProperty")
        Console.WriteLine ("Property Changed");
}

If at some point the property gets renamed the code will compile fine but a bug will be introduced as all those strings that contain the property name will remain unchanged. Evil.

So I have been toying with NHibernate lately and yesterday I was writing some repositories. Let’s assume that theoretically they look like this:

public class Repository <TEntity>
{
    public virtual TEntity FindById (object id)
    {
        ...
    }
}

public class UserRepository : Repository<User>
{
    public IList<User> FindByName (string name)
    {
        // query code
    }

    public IList<User> FindByEmail (string email)
    {
        // query code
    }
}

Both FindByName and FindByEmail in UserRepository will in theory contain the same queries but with different parameters. However I don’t really want to have to write individual queries so I considered a utility method:

public class Repository <TEntity>
{
    public virtual TEntity FindById (object id)
    {
        ...
    }

    protected virtual IList<TEntity> FindByProperty (string propertyName, object value)
    {
        string columnName = NHibernateUtil.GetPropertyColumnName<TEntity> (propertyName);

        // Query here, e.g: "SELECT .... WHERE .... columnName = value" , etc.
    }
}

public class UserRepository : Repository<User>
{
    public IList<User> FindByName (string name)
    {
        return base.FindByProperty ("Name", name);
    }

    public IList<User> FindByEmail (string email)
    {
        return base.FindByProperty ("Email", email);
    }
}

Again this is very bad code and I hated the idea of it, so I sat down and started thinking. I remembered reading somewhere about C# 3.0 Expression Trees so I did some research. When using the special Expression type this seems to tell the compiler to create and expose an AST to us of e.g. a lambda function’s body, so if we pass a property reference expression we can parse the AST and extract the property name from there.

The theory in practice:

public class UserRepository : Repository<User>
{
    public IList<User> FindByName (string name)
    {
        return base.FindByProperty (user => user.Name, name);
    }

    public IList<User> FindByEmail (string email)
    {
        return base.FindByProperty (user => user.Email, email);
    }
}

public class Repository <TEntity>
{
    public virtual TEntity FindById (object id)
    {
        ...
    }

    protected virtual IList<TEntity> FindByProperty (Expression<Func<TEntity, object>> propertyRefExpr,
                                                     object value)
    {
        string propertyName = GetPropertyName (propertyRefExpr);

        // Query code
    }

    private string GetPropertyName (Expression propertyRefExpr)
    {
        if (propertyRefExpr == null)
            throw new ArgumentNullException ("propertyRefExpr", "propertyRefExpr is null.");

        MemberExpression memberExpr = propertyRefExpr.Body as MemberExpression;
        if (memberExpr == null) {
            UnaryExpression unaryExpr = propertyRefExpr.Body as UnaryExpression;
            if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
                memberExpr = unaryExpr.Operand as MemberExpression;
        }

        if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
            return memberExpr.Member.Name;

        throw new ArgumentException ("No property reference expression was found.",
                         "propertyRefExpr");
    }
}

Also as a helper class:

public static class PropertyUtil
{
    public static string GetPropertyName<TObject> (this TObject type,
                                                   Expression<Func<TObject, object>> propertyRefExpr)
    {
        return GetPropertyNameCore (propertyRefExpr.Body);
    }

    public static string GetName<TObject> (Expression<Func<TObject, object>> propertyRefExpr)
    {
        return GetPropertyNameCore (propertyRefExpr.Body);
    }

    private static string GetPropertyNameCore (Expression propertyRefExpr)
    {
        if (propertyRefExpr == null)
            throw new ArgumentNullException ("propertyRefExpr", "propertyRefExpr is null.");

        MemberExpression memberExpr = propertyRefExpr as MemberExpression;
        if (memberExpr == null) {
            UnaryExpression unaryExpr = propertyRefExpr as UnaryExpression;
            if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
                memberExpr = unaryExpr.Operand as MemberExpression;
        }

        if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
            return memberExpr.Member.Name;

        throw new ArgumentException ("No property reference expression was found.",
                         "propertyRefExpr");
    }
}

As you can see it contains two generic methods that operate either on types:

string propertyName = PropertyUtil.GetName<User> (u => u.Email);

or instances:

User user = GetUser();
string propertyName = user.GetPropertyName (u => u.Email);

Great, isn’t it? And safe to refactor.

Of course there is no such thing as a free lunch and the use of expression trees comes at the expense of some performance.

BTW is the font size of the code snippets sufficiently readable or is it too tiny?

UPDATE: Added support for Convert expressions (implicit/explicit casting)

  • Twitter
  • Facebook
  • FriendFeed
  • StumbleUpon
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Reddit
  • Tumblr
  • PDF
  • Print
  • email
Rating: (No Ratings Yet)
Loading ... Loading ...
Comments
Published: Dec 4th, 2009 (Views: 581)
Categories: Coding
  • henon
    I improved it to work with valuetype properties too (which isn't the case with yours because of boxing) and added unit tests. here is what I got:

    using System;
    using System.Diagnostics;
    using System.Linq.Expressions;
    using System.Reflection;


    public static class ExpressionsExtension
    {
    /// <summary>
    /// Derives the name of a property from the given lambda expression and returns it as string.
    ///
    /// Example: DateTime.Now.DerivePropertyNameFromExpression(s => s.Ticks) returns "Ticks"
    /// </summary>
    /// <param name="expression">a lambda expression of the type x => x.PropertyName</param>
    /// <returns></returns>
    public static string DerivePropertyNameFromExpression<T, R>(this T type, Expression<Func<T, R>> expression)
    {
    var property_ref_expr = expression.Body;
    Debug.Assert(property_ref_expr != null);
    var member_expr = property_ref_expr as MemberExpression;
    if (member_expr == null || member_expr.Member.MemberType != MemberTypes.Property)
    throw new ArgumentException("No property reference expression was found.", "expression");
    return member_expr.Member.Name;
    }
    }


    #if DEBUG

    namespace Test
    {
    using NUnit.Framework;

    [TestFixture]
    public class ExpressionExtensionTest
    {
    [Test]
    public void TestNameDerivationFromValueTypeProperty()
    {
    Assert.AreEqual("Ticks", DateTime.Now.DerivePropertyNameFromExpression(s => s.Ticks));
    }

    [Test]
    public void TestNameDerivationFromReferenceTypeProperty()
    {
    Assert.AreEqual("Parent", new ExampleClass().DerivePropertyNameFromExpression(s => s.Parent));
    }

    public class ExampleClass
    {
    public ExampleClass Parent { get; set; }
    }
    }
    }

    #endif
  • You are right. I have updated the post with support for the Convert expression so that implicit and explicit casting are supported as well.
  • C# 3.0 Expression Trees are brilliant. Fluent NHibernate is entirely built around them, yielding extremely terse and readable code that's a walk in the park to refactor. Excellent stuff! :)
  • This is a brilliant hack.
  • Alan
    You could probably avoid some of the performance pain (if it actually turns out to be a bottleneck) with some clever caching of the expression tree and/or its result. Lazy <T> sounds like the ideal candidate here - you'll compute your string name once and then be able to re-use it for free as many times as you want.
  • Oskar
    I've been toying with this idea as well, and seems usable. On another note however, your use of GetPropertyColumnName() seems to indicate that you write SQL queries. You do know that this is not how you're supposed to use NHibernate right?
  • No worries I am aware of the Criteria API and the HQL :). Just haven't really got around to using it. Still only toying around with the different ways to do the mapping and reading around the in depth manual.
  • Adrian
    Nice article. The font size for the code snippets is a little too tiny, at least when read through Google Reader.
  • I added just such an extension method to my open source Sasa library a few months ago, though only the one overload and I use typed expression trees.

    Re:Stifu, yes this could be done, but requires compiler support. Modern languages are notorious in their lack of support for meta/reflective programming of the sort you describe.
  • Stifu
    About the performance tradeoff, this makes me wonder... why can't the strings just be generated at compile time rather than at run time? As in, the generated IL code would be just the same as if the strings had been hardcoded in the sources. Am I missing a case where this would not be desirable?
    I want to have my cake and eat it too.
  • uauuu I have the same problem and I was looking for a solution .. this solution fits me ....

    regards
    Paulo Aboim Pinto
    Odivelas - Portugal
blog comments powered by Disqus