Wednesday, April 6, 2011

Can I get specific metadata from a Func<T, object>?

Consider the following code:

string propertyName;
var dateList = new List<DateTime>() { DateTime.Now };
propertyName = dateList.GetPropertyName(dateTimeObject => dateTimeObject.Hour);

// I want the propertyName variable to now contain the string "Hour"

Here is the extension method:

public static string GetPropertyName<T>(this IList<T> list, Func<T, object> func) {
   //TODO: would like to dynamically determine which 
   // property is being used in the func function/lambda
}

Is there a way to do this? I thought that maybe this other method, using Expression<Func<T, object>> instead of Func<T, object> would give me more power to find what I need, but I am at a loss at how.

public static string GetPropertyName<T>(this IList<T> list, Expression<Func<T, object>> expr) {
   // interrogate expr to get what I want, if possible
}

This is the first time I have done anything this deep with Linq, so maybe I am missing something obvious. Basically I like the idea of passing in lambdas, so that I get compile-time checking, but I don't know that my idea on how I can use them in this particular case will work.

Thanks

From stackoverflow
  • Something like this should do the trick:

    public static string GetPropertyName<T>(this IList<T> list, Expression<Func<T, object>> expr) {
      MemberExpression member_expression = expr.Body as MemberExpression;
      if (member_expression == null)
          throw new ArgumentNullException("member_expression");
      MemberInfo member = member_expression.Member;
      PropertyInfo property = member as PropertyInfo;
      string proname = memeber.name;
    }
    

    WARNING: Air Code!

    Jason Bunting : Thanks for answering Nathan, I appreciate it. You were close enough, and since you said "something like this should do the trick," you were not technically wrong! I will accept @gcores' answer because his was correct without me having to make changes. I still gave you a +1 for answering though!
    sighohwell : Theres a minor difference between Nathan's and gcores's answers with regard to some fringe cases such as nullable decimals (if I remember correctly!). For those properties the body of the expression isn't a MemberExpression but a UnaryExpression (dealt with in gcores's answer).
    Nathan W : @Jason Thats cool, I would have accepted his answer as well as it is more complete then mine.
    Jason Bunting : @sighohwell: I know, that is why I accepted @gcores' answer and felt that @Nathan's was good enough to deserve some credit, since it would have helped point me in the right direction if @gcores hadn't posted.
  • This is the version I use, it returns a PropertyInfo, but getting the name is trivial.

    public static PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)  
        {
            MemberExpression memberExpression = null;
    
            if (expression.Body.NodeType == ExpressionType.Convert)
            {
                memberExpression = ((UnaryExpression) expression.Body).Operand as MemberExpression;
            }
            else if (expression.Body.NodeType == ExpressionType.MemberAccess)
            {
                memberExpression = expression.Body as MemberExpression;
            }
    
            if (memberExpression == null)
            {
                throw new ArgumentException("Not a member access", "expression");
            }
    
            return memberExpression.Member as PropertyInfo;
        }
    
    Jason Bunting : Awesome, this was perfect. Now my hack is complete! :P Ah, I hate having to come up with these workarounds to get stuff to work "like it should." I mean, it's fun and all, but just too painful when there is real work to do. Thank you!
  • Hi,

    here is a very easy and fast way to do it on this blog: http://blog.bittercoder.com/PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx

    So given:

    Func func = Name => "Value";

    You can get the lambda parameter "Name" from the function delegate by calling:

    func.Method.GetParameters()[0].Name (would return "Name")

    Here's the revised Hash method from Andrey:

    public Dictionary<string, T> Hash<T>(params Func<string, T>[] args)
    where T : class
    {
        var items = new Dictionary<string, T>();
        foreach (var func in args)
        {
            var item = func(null);
            items.Add(func.Method.GetParameters()[0].Name, item);
        }
        return items;
    }
    

    Hope it helps, Patrick

  • Just a side note: func.Method.GetParameters()[0].Name is extremelly fast when compared with compiling the lambda and extracting the member expression, then the member info, then the name.

0 comments:

Post a Comment