Tuesday, February 3, 2009

Static typed property names

String names of properties is common problem in dynamically developed software.

Common usages of property names literals
Common usages of such literal are:
  • Property names in Nhibernate queries
    Suppose you want to query all user for specific value of property “LastProjectCode” with detached criteria class:
    DetachedCriteria query = DetachedCriteria.For(typeof(User))
    .Add(Expression.Eq("LastProjectCode", 12345));
    
  • Implementing INotifyPropertyChanged interface
    Standard implementation unfortunately have to pass property name as literal when event is raised. It looks like below:

    public int LastProjectCode
    {
        get { return this.lastProjectCode; }
        set{
            if (this.lastProjectCode != value)
            {
                this.lastProjectCode = value;
                OnPropertyChanged(
                  new PropertyChangedEventArgs("LastProjectCode"));
            }
        }
    }
    
  • Providing error information with IDataErrorInfo
    Component using entity (like user) queries it for error by calling property this[string columnName]. Implementation is usually based on many if condition checking for column name like below:
    public string this[string columnName]
    {
        get{
            string result = null;
            if (columnName == "LastProjectCode" && LastProjectCode < 0)
            {
                result = "Last project code cannot be negative.";
            } 
    
            return result;
        }
    }
    

Disadvantages
All mentioned situation have at least 2 major disadvantages
  • Possibility to mistype name
  • Hard refactoring (since refactoring cannot determine if name of property in literal is really name of property)
To avoid it I have wrote TypeHelper class where you can find out property name in static way like this:  

Solution
string propertyName = TypeHelper.GetPropertyName(u => u.LastProjectCode);
Now both disadvantages disappeared
  • You cannot misspell word since compiler checks it
  • Refactoring is possible because refactoring engine knows all type names at compile type and none of them is "hidden" in literals
Usage
Here is how you can extract property names and PropertyInfo object in staticly typed way:
string propertyName = TypeHelper.GetPropertyName(u => u.LastProjectCode);

PropertyInfo property1 = TypeHelper.GetProperty((SomeClass o) => o.InstanceProperty.Length);

PropertyInfo property2 = TypeHelper.GetProperty(() => SomeClass.StaticProperty.Length);

Implementation
public class TypeHelper
{
    private static PropertyInfo GetPropertyInternal(LambdaExpression p)
    {
        MemberExpression memberExpression; 

        if (p.Body is UnaryExpression)
        {
            UnaryExpression ue = (UnaryExpression)p.Body;
            memberExpression = (MemberExpression)ue.Operand;
        }
        else
        {
            memberExpression = (MemberExpression)p.Body;
        } 

        return (PropertyInfo)(memberExpression).Member;
    } 

    public static string GetPropertyName<TObject>(Expression<Func<TObject, object>> p)
    {
        return GetPropertyNameInternal(p);
    } 

    public static string GetPropertyName<TObject, T>(Expression<Func<TObject, T>> p)
    {
        return GetPropertyNameInternal(p);
    } 

    public static string GetPropertyName<T>(Expression<Func<T>> p)
    {
        return GetPropertyNameInternal(p);
    } 

    private static string GetPropertyNameInternal(LambdaExpression p)
    {
        return GetPropertyInternal(p).Name;
    } 

    public static PropertyInfo GetProperty<TObject>(Expression<Func<TObject, object>> p)
    {
        return GetPropertyInternal(p);
    } 

    public static PropertyInfo GetProperty<TObject, T>(Expression<Func<TObject, T>> p)
    {
        return GetPropertyInternal(p);
    } 

    public static PropertyInfo GetProperty<T>(Expression<Func<T>> p)
    {
        return GetPropertyInternal(p);
    }
}