Ivan's Brain Dump




read

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)

Blog Logo

Ivan Zlatev


Published

Image

Ivan's Brain Dump

Back to Overview