43

I created a method in C# to get methodname

public string GetCorrectPropertyName<T>(Expression<Func<T, string>> expression)
{
   return ((MemberExpression)expression.Body).Member.Name; // Failure Point
}

and calling it as

string lcl_name = false;
public string Name
{
get { return lcl_name ; }
set 
    {
        lcl_name = value;
        OnPropertyChanged(GetCorrectPropertyName<ThisClassName>(x => x.Name));
}
}

This works fine if property is string and for all other types gives this exception:

Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MemberExpression'.

  1. I changed string to object in method signature, but then it fails again.
  2. I changed calling from x => x.PropertyName to x => Convert.ToString(x.PropertyName) and it still fails

Where am I wrong?

Community
  • 1
  • 1
Nikhil Agrawal
  • 47,018
  • 22
  • 121
  • 208
  • 2
    IMO it's better to use a variation of this where the helper takes an `Expression>`. This changes the call site syntax to `GetCorrectPropertyName(() => this.Name)`, which in my view is both better to type (no need to give the generic type parameter) and better to read (`this.Name` conveys the intent extremely well). – Jon Sep 14 '12 at 10:01
  • @Jon: Buddy feel free to add your answer. If better than current answer, I will definately accept your's. – Nikhil Agrawal Sep 14 '12 at 10:04
  • I wouldn't want to do that because it would usurp the intent of your question. But you can easily grab the code for it from [here](http://compositewpf.codeplex.com/SourceControl/changeset/view/65392#1024364), Microsoft does exactly this in Prism. – Jon Sep 14 '12 at 10:08
  • There is nothing that prevents you from doing that in Prism 2 (or entirely outside of Prism for that matter). – Jon Sep 14 '12 at 10:34
  • @Jon: But wouldn't that method(of prism) be limited to properties only or can it be applied to methods also? – Nikhil Agrawal Sep 19 '12 at 10:53
  • Of course it's limited to properties, it explicitly throws an exception if you use it on anything else. It shouldn't be hard to convert it though. – Jon Sep 19 '12 at 10:58
  • @Jon: How to convert it? – Nikhil Agrawal Sep 19 '12 at 11:04

5 Answers5

71

You need a separate line to extract the Member where the input expression is a Unary Expression.

Just converted this from VB.Net, so might be slightly off - let me know if I need to make any minor tweaks:

public string GetCorrectPropertyName<T>(Expression<Func<T, Object>> expression)
{
    if (expression.Body is MemberExpression) {
        return ((MemberExpression)expression.Body).Member.Name;
    }
    else {
        var op = ((UnaryExpression)expression.Body).Operand;
        return ((MemberExpression)op).Member.Name;
    }                
}

The VB version is:

Public Shared Function GetCorrectPropertyName(Of T) _
             (ByVal expression As Expression(Of Func(Of T, Object))) As String
    If TypeOf expression.Body Is MemberExpression Then
        Return DirectCast(expression.Body, MemberExpression).Member.Name
    Else
        Dim op = (CType(expression.Body, UnaryExpression).Operand)
        Return DirectCast(op, MemberExpression).Member.Name
    End If
End Function

Note that the input expression does not return string necessarily - that constrains you to only reading properties that return strings.

KyleMit
  • 30,350
  • 66
  • 462
  • 664
Jon Egerton
  • 40,401
  • 11
  • 97
  • 129
  • 4
    One wonders why something like this hasn't yet been included in the BCL or an extension. It's incredibly useful. – BoltClock Sep 14 '12 at 08:21
  • Just wanted to know how to use/call it for properties and methods (VB.Net's Sub's and Function's)? – Nikhil Agrawal Sep 19 '12 at 11:03
  • @NikhilAgrawal: You're best to ask a new question, reference back to this one, and explain your requiremets fully. – Jon Egerton Sep 19 '12 at 11:10
  • I want an Extension Method which will return name of property or method (VB.Net's Sub's and Function's) That property/method can or cannot be the same from where it is called. – Nikhil Agrawal Sep 19 '12 at 11:23
15

This is apparently related to boxing/unboxing. Lambda expressions returning value types that require boxing will be represented as UnaryExpressions whereas those that return reference types will be represented as MemberExpressions.

Scott Munro
  • 13,369
  • 3
  • 74
  • 80
  • 4
    This elaborates on the highest-rated answer and gives further insight for those that aren't satisfied with just the answer. – gregsonian Jun 06 '18 at 17:46
4

After asking this question(yes I am OP) i received comments on question from Jon

and I came up with this

public string ResolvePropertyName<TEntity>(Expression<Func<TEntity>> expression)
{
try {
    if (expression == null) {
        Throw New ArgumentNullException("propertyExpression")
    }

    object memberExpression = expression.Body as MemberExpression;
    if (memberExpression == null) {
        Throw New ArgumentException("The expression is not a member access expression.", "propertyExpression")
    }

    object property = memberExpression.Member as PropertyInfo;
    if (property == null) {
        Throw New ArgumentException("The member access expression does not access a property.", "propertyExpression")
    }

    object getMethod = property.GetGetMethod(true);
    if (getMethod.IsStatic) {
        Throw New ArgumentException("The referenced property is a static property.", "propertyExpression")
    }
    return memberExpression.Member.Name;
} catch (Exception ex) {
    return string.Empty;
}
}
Community
  • 1
  • 1
Nikhil Agrawal
  • 47,018
  • 22
  • 121
  • 208
1

A modern version of the answer above.

private static string GetPropertyName<T>(Expression<Func<T, object>> expression) 
=> expression.Body switch
{
    MemberExpression expr => expr.Member.Name,
    UnaryExpression expr => ((MemberExpression)expr.Operand).Member.Name,
    _ => throw new ArgumentException($"Argument {nameof(expression)} is not a property expression.", nameof(expression)),
};
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
Henry Yu
  • 11
  • 1
  • 1
0

In case you have to handle conditional expressions add this:

else if (expression.Body is ConditionalExpression expr)
{
    return ((MemberExpression)((bool)(System.Linq.Expressions.Expression.Lambda(expr.Test).Compile().DynamicInvoke())
        ? expr.IfTrue
        : expr.IfFalse)).Member.Name;
}
IngoB
  • 2,552
  • 1
  • 20
  • 35