42

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
  • 44,717
  • 22
  • 115
  • 201
  • 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

4 Answers4

70

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
  • 35,223
  • 60
  • 418
  • 600
Jon Egerton
  • 38,633
  • 11
  • 95
  • 128
14

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,059
  • 3
  • 70
  • 79
  • 3
    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
  • 44,717
  • 22
  • 115
  • 201
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
  • 100,019
  • 29
  • 193
  • 278
Henry Yu
  • 11
  • 1
  • 1