593

Is there a better way to get the Property name when passed in via a lambda expression? Here is what i currently have.

eg.

GetSortingInfo<User>(u => u.UserId);

It worked by casting it as a memberexpression only when the property was a string. because not all properties are strings i had to use object but then it would return a unaryexpression for those.

public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, 
    Expression<Func<T, object>> action) where T : class
{
    var expression = GetMemberInfo(action);
    string name = expression.Member.Name;

    return GetInfo(html, name);
}

private static MemberExpression GetMemberInfo(Expression method)
{
    LambdaExpression lambda = method as LambdaExpression;
    if (lambda == null)
        throw new ArgumentNullException("method");

    MemberExpression memberExpr = null;

    if (lambda.Body.NodeType == ExpressionType.Convert)
    {
        memberExpr = 
            ((UnaryExpression)lambda.Body).Operand as MemberExpression;
    }
    else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
    {
        memberExpr = lambda.Body as MemberExpression;
    }

    if (memberExpr == null)
        throw new ArgumentException("method");

    return memberExpr;
}
casperOne
  • 73,706
  • 19
  • 184
  • 253
Schotime
  • 15,707
  • 10
  • 46
  • 75
  • 1
    Better as in nicer code? I don't think so. The typechecking only extends to the overall expression, so you really do need the checks you have in at runtime. :( – MichaelGG Mar 23 '09 at 03:56
  • 1
    Yeah...was just wondering if there was a better way to do it, as it felt a little hacky to me. But if thats it then cool. thanks. – Schotime Mar 23 '09 at 04:20
  • 2
    I updated re your comment; but using a lambda to get a string so that you can use dynamic LINQ strikes me as doing things backwards... if you use a lambda, use a lambda ;-p You don't have to do the entire query in one step - you could use "regular/lambda" OrderBy, "dynamic LINQ/string" Where, etc. – Marc Gravell Mar 23 '09 at 08:37
  • 1
    It is to generate the parameters to be passed through querystring on URL to tell it which order to display the list. I could not use dynamic linq but then i would have to have a case statement for every column. The GetInfo() is called as the 3rd parameter on the actionlink helper. – Schotime Mar 23 '09 at 22:07
  • 2
    possible duplicate of [get-property-name-and-type-using-lambda-expression](http://stackoverflow.com/questions/273941/get-property-name-and-type-using-lambda-expression) – nawfal Dec 11 '13 at 23:13
  • 5
    A note to everyone: Use the `MemberExpression` approach listed here only to get the **name** of the member, **not** to get the actual `MemberInfo` itself, because the `MemberInfo` returned is not guaranteed to be of the reflected type in certain "dervied : base" scenarios. See [lambda-expression-not-returning-expected-memberinfo](http://stackoverflow.com/questions/6658669/lambda-expression-not-returning-expected-memberinfo?lq=1). Tripped me once. The accepted answer too suffers from this. – nawfal Dec 14 '13 at 09:19
  • 1
    @nawfal guess you meant to write "derived" – George Birbilis Mar 24 '16 at 01:55
  • 4
    from C# 6, you can simply use `nameof()`, eg: `nameof(User.UserId)`. No helper method is needed and it is replaced at compile time! – S.Serpooshan Sep 17 '18 at 08:24

23 Answers23

405

I recently did a very similar thing to make a type safe OnPropertyChanged method.

Here's a method that'll return the PropertyInfo object for the expression. It throws an exception if the expression is not a property.

public static PropertyInfo GetPropertyInfo<TSource, TProperty>(
    TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda)
{
    if (propertyLambda.Body is not MemberExpression member)
    {
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));
    }

    if (member.Member is not PropertyInfo propInfo)
    {
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));
    }

    Type type = typeof(TSource);
    if (propInfo.ReflectedType != null && type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
    {
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));
    }

    return propInfo;
}

The source parameter is used so the compiler can do type inference on the method call. You can do the following

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
Hille
  • 2,123
  • 22
  • 39
Cameron MacFarland
  • 70,676
  • 20
  • 104
  • 133
  • 9
    Why is the last check regarding TSource in there? The lambda is strongly typed so I don't think it's necessary. – HappyNomad Apr 22 '12 at 00:02
  • 21
    Also, as of 2012, type inference works fine without the source parameter. – HappyNomad Apr 22 '12 at 00:56
  • 6
    @HappyNomad Imagine an object which has as a member, an instance of a third type. `u => u.OtherType.OtherTypesProperty` would create such a case that the last statement is checking for. – joshperry Dec 12 '12 at 15:13
  • 6
    The last if statement should be: `if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))` to allow for interfaces too. – Graham King Apr 18 '13 at 13:14
  • 9
    @GrayKing wouldn't that be the same as just `if(!propInfo.ReflectedType.IsAssignableFrom(type))`? – Connell Jan 23 '14 at 14:24
  • 6
    ToString() are redundant in exception message formatting – Darius Kucinskas Mar 06 '15 at 09:22
  • 3
    Can we change signature to take Type of class as first argument? Then we may not need to create 'someUserObject' and instead send typeof(userclass). Is that possible? – Shishir Gupta Jul 11 '15 at 18:57
  • 2
    You may also use as a extension method adding "this" prefix to source and making a static method inside a static class – Daniel Aragão Mar 12 '20 at 14:10
238

I found another way you can do it was to have the source and property strongly typed and explicitly infer the input for the lambda. Not sure if that is correct terminology but here is the result.

public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
    var expression = (MemberExpression)action.Body;
    string name = expression.Member.Name;

    return GetInfo(html, name);
}

And then call it like so.

GetInfo((User u) => u.UserId);

and voila it works.

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Schotime
  • 15,707
  • 10
  • 46
  • 75
  • 5
    This solution should be a little bit updated. Please check the following article - here is a [**link**](http://stackoverflow.com/questions/6658669/lambda-expression-not-returning-expected-memberinfo) – Pavel Cermak Oct 02 '14 at 11:34
  • 3
    Its only an option if you do ASP.Net MVC and only for the UI layer (HtmlHelper). – Marc Jul 27 '16 at 08:27
  • 6
    starting from c# 6.0 you can use `GetInfo(nameof(u.UserId))` – Vladislav Aug 22 '17 at 08:14
  • 3
    In net core I had to use this: `var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name` – Falk Nov 25 '19 at 17:10
  • 1
    This solution does not handle `UnaryExpression`s. It cannot be used for `double` or `int`. While @Falk's comment does not allow `string` to be used (because it does not handle `MemberExpression`s). There are other solution without these drawbacks. – Jack Miller Apr 20 '21 at 06:29
157

I was playing around with the same thing and worked this up. It's not fully tested but seems to handle the issue with value types (the unaryexpression issue you ran into)

public static string GetName(Expression<Func<object>> exp)
{
    MemberExpression body = exp.Body as MemberExpression;

    if (body == null) {
       UnaryExpression ubody = (UnaryExpression)exp.Body;
       body = ubody.Operand as MemberExpression;
    }

    return body.Member.Name;
}
joshperry
  • 41,167
  • 16
  • 88
  • 103
M Thelen
  • 1,663
  • 1
  • 11
  • 9
  • 3
    tried this recently (from [another question](http://stackoverflow.com/questions/13588022/exclude-property-from-serialization-via-custom-attribute-json-net/16647343#16647343)), found out it does not handle subproperties: `o => o.Thing1.Thing2` would return `Thing2`, not `Thing1.Thing2`, which is incorrect if you're trying to use it in EntityFramework includes – drzaus Jun 20 '13 at 17:59
  • 1
    AKA (field.Body is UnaryExpression ? ((UnaryExpression)field.Body).Operand : field.Body) as MemberExpression –  Oct 14 '16 at 00:44
59
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

This handles member and unary expressions. The difference being that you will get a UnaryExpression if your expression represents a value type whereas you will get a MemberExpression if your expression represents a reference type. Everything can be cast to an object, but value types must be boxed. This is why the UnaryExpression exists. Reference.

For the sakes of readability (@Jowen), here's an expanded equivalent:

public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    if (object.Equals(Field, null))
    {
        throw new NullReferenceException("Field is required");
    }

    MemberExpression expr = null;

    if (Field.Body is MemberExpression)
    {
        expr = (MemberExpression)Field.Body;
    }
    else if (Field.Body is UnaryExpression)
    {
        expr = (MemberExpression)((UnaryExpression)Field.Body).Operand;
    }
    else
    {
        const string Format = "Expression '{0}' not supported.";
        string message = string.Format(Format, Field);

        throw new ArgumentException(message, "Field");
    }

    return expr.Member.Name;
}
Community
  • 1
  • 1
Paul Fleming
  • 24,238
  • 8
  • 76
  • 113
  • @flem, I omit for readability, is there any problem. LambdaExpressions.GetName(m => m.Quantity) – Søren Aug 24 '15 at 08:10
  • 1
    @soren I'm sure someone more tuned in than me may suggest that you're opening your code up to the potential of unnecessary boxing/unboxing when passing expressions of value types, but because the expression is never compiled and evaluated in this method, it is probably not a problem. – Paul Fleming Aug 25 '15 at 14:50
57

With C# 7 pattern matching:

public static string GetMemberName<T>(this Expression<T> expression)
{
    switch (expression.Body)
    {
        case MemberExpression m:
            return m.Member.Name;
        case UnaryExpression u when u.Operand is MemberExpression m:
            return m.Member.Name;
        default:
            throw new NotImplementedException(expression.GetType().ToString());
    }
}

Example:

public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, 
    Expression<Func<T, object>> action) where T : class
{
    var name = action.GetMemberName();
    return GetInfo(html, name);
}

[Update] C# 8 pattern matching:

public static string GetMemberName<T>(this Expression<T> expression) => expression.Body switch
{
    MemberExpression m => m.Member.Name,
    UnaryExpression u when u.Operand is MemberExpression m => m.Member.Name,
    _ => throw new NotImplementedException(expression.GetType().ToString())
};
Brandon Minnick
  • 13,342
  • 15
  • 65
  • 123
akhansari
  • 954
  • 9
  • 13
27

now in C# 6 you can simply use nameof like this nameof(User.UserId)

which has many benefits, among them is that this is done at compile time, not runtime.

https://msdn.microsoft.com/en-us/magazine/dn802602.aspx

Community
  • 1
  • 1
Maslow
  • 18,464
  • 20
  • 106
  • 193
21

This is a general implementation to get the string name of fields/properties/indexers/methods/extension methods/delegates of struct/class/interface/delegate/array. I have tested with combinations of static/instance and non-generic/generic variants.

//involves recursion
public static string GetMemberName(this LambdaExpression memberSelector)
{
    Func<Expression, string> nameSelector = null;  //recursive func
    nameSelector = e => //or move the entire thing to a separate recursive method
    {
        switch (e.NodeType)
        {
            case ExpressionType.Parameter:
                return ((ParameterExpression)e).Name;
            case ExpressionType.MemberAccess:
                return ((MemberExpression)e).Member.Name;
            case ExpressionType.Call:
                return ((MethodCallExpression)e).Method.Name;
            case ExpressionType.Convert:
            case ExpressionType.ConvertChecked:
                return nameSelector(((UnaryExpression)e).Operand);
            case ExpressionType.Invoke:
                return nameSelector(((InvocationExpression)e).Expression);
            case ExpressionType.ArrayLength:
                return "Length";
            default:
                throw new Exception("not a proper member selector");
        }
    };

    return nameSelector(memberSelector.Body);
}

This thing can be written in a simple while loop too:

//iteration based
public static string GetMemberName(this LambdaExpression memberSelector)
{
    var currentExpression = memberSelector.Body;

    while (true)
    {
        switch (currentExpression.NodeType)
        {
            case ExpressionType.Parameter:
                return ((ParameterExpression)currentExpression).Name;
            case ExpressionType.MemberAccess:
                return ((MemberExpression)currentExpression).Member.Name;
            case ExpressionType.Call:
                return ((MethodCallExpression)currentExpression).Method.Name;
            case ExpressionType.Convert:
            case ExpressionType.ConvertChecked:
                currentExpression = ((UnaryExpression)currentExpression).Operand;
                break;
            case ExpressionType.Invoke:
                currentExpression = ((InvocationExpression)currentExpression).Expression;
                break;
            case ExpressionType.ArrayLength:
                return "Length";
            default:
                throw new Exception("not a proper member selector");
        }
    }
}

I like the recursive approach, though the second one might be easier to read. One can call it like:

someExpr = x => x.Property.ExtensionMethod()[0]; //or
someExpr = x => Static.Method().Field; //or
someExpr = x => VoidMethod(); //or
someExpr = () => localVariable; //or
someExpr = x => x; //or
someExpr = x => (Type)x; //or
someExpr = () => Array[0].Delegate(null); //etc

string name = someExpr.GetMemberName();

to print the last member.

Note:

  1. In case of chained expressions like A.B.C, "C" is returned.

  2. This doesn't work with consts, array indexers or enums (impossible to cover all cases).

nawfal
  • 70,104
  • 56
  • 326
  • 368
20

There's an edge case when it comes to Array.Length. While 'Length' is exposed as a property, you can't use it in any of the previously proposed solutions.

using Contract = System.Diagnostics.Contracts.Contract;
using Exprs = System.Linq.Expressions;

static string PropertyNameFromMemberExpr(Exprs.MemberExpression expr)
{
    return expr.Member.Name;
}

static string PropertyNameFromUnaryExpr(Exprs.UnaryExpression expr)
{
    if (expr.NodeType == Exprs.ExpressionType.ArrayLength)
        return "Length";

    var mem_expr = expr.Operand as Exprs.MemberExpression;

    return PropertyNameFromMemberExpr(mem_expr);
}

static string PropertyNameFromLambdaExpr(Exprs.LambdaExpression expr)
{
         if (expr.Body is Exprs.MemberExpression)   return PropertyNameFromMemberExpr(expr.Body as Exprs.MemberExpression);
    else if (expr.Body is Exprs.UnaryExpression)    return PropertyNameFromUnaryExpr(expr.Body as Exprs.UnaryExpression);

    throw new NotSupportedException();
}

public static string PropertyNameFromExpr<TProp>(Exprs.Expression<Func<TProp>> expr)
{
    Contract.Requires<ArgumentNullException>(expr != null);
    Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);

    return PropertyNameFromLambdaExpr(expr);
}

public static string PropertyNameFromExpr<T, TProp>(Exprs.Expression<Func<T, TProp>> expr)
{
    Contract.Requires<ArgumentNullException>(expr != null);
    Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);

    return PropertyNameFromLambdaExpr(expr);
}

Now example usage:

int[] someArray = new int[1];
Console.WriteLine(PropertyNameFromExpr( () => someArray.Length ));

If PropertyNameFromUnaryExpr didn't check for ArrayLength, "someArray" would be printed to the console (compiler seems to generate direct access to the backing Length field, as an optimization, even in Debug, thus the special case).

kornman00
  • 808
  • 10
  • 27
17

Here's an update to method proposed by Cameron. The first parameter is not required.

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    Expression<Func<TSource, TProperty>> propertyLambda)
{
    Type type = typeof(TSource);

    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(string.Format(
            "Expresion '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

You can do the following:

var propertyInfo = GetPropertyInfo<SomeType>(u => u.UserID);
var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);

Extension methods:

public static PropertyInfo GetPropertyInfo<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    return GetPropertyInfo(propertyLambda);
}

public static string NameOfProperty<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    PropertyInfo prodInfo = GetPropertyInfo(propertyLambda);
    return prodInfo.Name;
}

You can:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
Community
  • 1
  • 1
Adrian
  • 7,745
  • 5
  • 28
  • 28
  • No he will not infer `u` as some type, he can't do that because there is not type to infer. What you can do is `GetPropertyInfo(u => u.UserID)` – Lucas Dec 14 '16 at 18:38
  • Well, using `GetPropertyInfo(u => u.UserID);` gives me _"Using .GetPropertyInfo(Expression>) requires 2 type arguments."_ The alternative `GetPropertyInfo((SomeType u) => u.UserID)` does work. What could be the problem? (not using extension methods but a static me). – Guillermo Prandi Sep 12 '20 at 20:11
16

I've found that some of the suggested answers which drill down into the MemberExpression/UnaryExpression don't capture nested/subproperties.

ex) o => o.Thing1.Thing2 returns Thing1 rather than Thing1.Thing2.

This distinction is important if you're trying to work with EntityFramework DbSet.Include(...).

I've found that just parsing the Expression.ToString() seems to work fine, and comparatively quickly. I compared it against the UnaryExpression version, and even getting ToString off of the Member/UnaryExpression to see if that was faster, but the difference was negligible. Please correct me if this is a terrible idea.

The Extension Method

/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas.  Technique @via https://stackoverflow.com/a/16647343/1037948
/// </summary>
/// <remarks>Cheats and uses the tostring output -- Should consult performance differences</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <param name="delimiter">Expression toString delimiter to split from lambda param</param>
/// <param name="endTrim">Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end</param>
/// <returns>indicated property name</returns>
public static string GetPropertyName<TModel, TValue>(this Expression<Func<TModel, TValue>> propertySelector, char delimiter = '.', char endTrim = ')') {

    var asString = propertySelector.ToString(); // gives you: "o => o.Whatever"
    var firstDelim = asString.IndexOf(delimiter); // make sure there is a beginning property indicator; the "." in "o.Whatever" -- this may not be necessary?

    return firstDelim < 0
        ? asString
        : asString.Substring(firstDelim+1).TrimEnd(endTrim);
}//--   fn  GetPropertyNameExtended

(Checking for the delimiter might even be overkill)

Demo (LinqPad)

Demonstration + Comparison code -- https://gist.github.com/zaus/6992590

Community
  • 1
  • 1
drzaus
  • 24,171
  • 16
  • 142
  • 201
  • 1
    + 1 very interesting. Have you continued to use this method in your own code? does it work ok? have you discovered any edge cases? – Benjamin Gale Jul 28 '13 at 17:55
  • I fail to see your idea. Going by the answer you linked `o => o.Thing1.Thing2` doesn't return `Thing1` as you say but `Thing2`. In fact your answer returns something like `Thing1.Thing2` which may or may not be desired. – nawfal Oct 11 '13 at 19:57
  • Doesn't work with the case korman cautions: http://stackoverflow.com/a/11006147/661933. Always better to avoid hacks. – nawfal Oct 11 '13 at 19:58
  • @nawfal #1 -- the original problem is that you _want_ `Thing1.Thing2`, never `Thing1`. I said `Thing2` meaning the _value_ of `o.Thing1.Thing2`, which is the point of the predicate. I'll update the answer to reflect that intention. – drzaus Oct 15 '13 at 14:05
  • @drzaus sorry I'm still not getting you. Genuinely trying to understand. Why would you say that other answers here return `Thing1`? I dont think it retuns that at all. – nawfal Oct 15 '13 at 14:14
  • @nawfal -- sorry, I confused myself with the purpose of this, we're trying to get the _property name_ (not the _value_) in lieu of using magic strings. Example: Using Entity Framework to retrieve descendant elements with the base query via [`DbSet.Include("Child")`](http://msdn.microsoft.com/en-us/library/gg696785(v=vs.103).aspx) to eager-load the `Child` entity attached to `Parent`. `"Child"` is now a magic string, meaning if we change the property name code breaks, so we'd rather call something like `.Include(o => o.Child)`. _(continued)_ – drzaus Oct 15 '13 at 14:20
  • @nawfal (continued) So using the other solutions posted here, it's fine. But if you want to also/instead include descendants of `Child` (`.Include("Child.Grandbaby")` originally) you cannot use the other solutions -- they still stop at returning `Child`. The extension I propose gives you the fully-qualified property `"Child.Grandbaby"` from the expression `o => o.Child.Grandbaby`. – drzaus Oct 15 '13 at 14:22
  • @nawfal -- to illustrate: https://gist.github.com/zaus/6992590 -- and it is clear that the `Array.Length` is a gotcha as you mentioned in #2 comment; not really sure what to do about that, but I would propose that most of the time when you are using the expression property names it won't be for that edge case. – drzaus Oct 15 '13 at 14:45
  • @nawfal last reply, I promise :) -- Another usage scenario: JSON.Net `DefaultContractResolver` ignore properties by name using strongly-typed expression: http://stackoverflow.com/a/16647343/1037948 – drzaus Oct 15 '13 at 14:52
  • @drzaus no I love discussions, don't worry :) Thanks for the theory, but I knew.. The point I'm trying to make is that most solutions here including the one you linked results in giving `Grandbaby`, not `Child` as you say. That's my whole point. And I dont have an argument that your method wouldn't give `Child.Grandbaby` in full, though I find it very brittle. The expression is walked from right to left. – nawfal Oct 15 '13 at 14:55
  • it doesn't work with a expression like this: Expression> som = a => a.Id; – SuperJMN Nov 24 '17 at 11:26
  • @SuperJMN when you say "it doesn't work" what do you mean? https://dotnetfiddle.net/PKvAxT says it does -- if you're seeing `a => Convert(a.Id)` it's because of `object` boxing. – drzaus Nov 24 '17 at 15:59
  • @drzaus Sorry, but I didn't have time to give more details in my previous comment :) Yes, I was referring exactly to that. I would like your code also worked in that case. I expected it to return "Id" instead :) – SuperJMN Nov 24 '17 at 19:25
  • @SuperJMN updated my fiddle, my extension method actually does just give you "Id" even with object boxing -- it substrings to the first "." and trims the last ")". Plain `.ToString()` isn't my code, it's a .NET thing. – drzaus Nov 29 '17 at 15:59
  • @drzaus Thanks! but I don't see the updated code. Have you updated the GitHub Gist? :) – SuperJMN Nov 29 '17 at 17:11
  • @SuperJMN what are you talking about? just use *the extension method* (_not_ plain `ToString`) like in this answer and the gist and the fiddle and you'll get "Id" like you wanted. – drzaus Dec 07 '17 at 21:40
7

I"m using an extension method for pre C# 6 projects and the nameof() for those targeting C# 6.

public static class MiscExtentions
{
    public static string NameOf<TModel, TProperty>(this object @object, Expression<Func<TModel, TProperty>> propertyExpression)
    {
        var expression = propertyExpression.Body as MemberExpression;
        if (expression == null)
        {
            throw new ArgumentException("Expression is not a property.");
        }

        return expression.Member.Name;
    }
}

And i call it like:

public class MyClass 
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
    public int[] Property3 { get; set; }
    public Subclass Property4 { get; set; }
    public Subclass[] Property5 { get; set; }
}

public class Subclass
{
    public int PropertyA { get; set; }
    public string PropertyB { get; set; }
}

// result is Property1
this.NameOf((MyClass o) => o.Property1);
// result is Property2
this.NameOf((MyClass o) => o.Property2);
// result is Property3
this.NameOf((MyClass o) => o.Property3);
// result is Property4
this.NameOf((MyClass o) => o.Property4);
// result is PropertyB
this.NameOf((MyClass o) => o.Property4.PropertyB);
// result is Property5
this.NameOf((MyClass o) => o.Property5);

It works fine with both fields and properties.

kalitsov
  • 1,319
  • 3
  • 20
  • 33
6

This might be optimal

public static string GetPropertyName<TResult>(Expression<Func<TResult>> expr)
{
    var memberAccess = expr.Body as MemberExpression;
    var propertyInfo = memberAccess?.Member as PropertyInfo;
    var propertyName = propertyInfo?.Name;

    return propertyName;
}
6

Well, there's no need to call .Name.ToString(), but broadly that is about it, yes. The only consideration you might need is whether x.Foo.Bar should return "Foo", "Bar", or an exception - i.e. do you need to iterate at all.

(re comment) for more on flexible sorting, see here.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    Yeah...its only a first level thing, used for generating a sorting column link. eg. If I have a model and i want to display the column name to sort by i can use a strongly typed link to the object to get the property name for which dynamic linq won't have a cow over. cheers. – Schotime Mar 23 '09 at 05:30
  • `ToString` should give ugly results for unary expressions. – nawfal Oct 11 '13 at 20:00
5

I leave this function if you want to get multiples fields:

/// <summary>
    /// Get properties separated by , (Ex: to invoke 'd => new { d.FirstName, d.LastName }')
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="exp"></param>
    /// <returns></returns>
    public static string GetFields<T>(Expression<Func<T, object>> exp)
    {
        MemberExpression body = exp.Body as MemberExpression;
        var fields = new List<string>();
        if (body == null)
        {
            NewExpression ubody = exp.Body as NewExpression;
            if (ubody != null)
                foreach (var arg in ubody.Arguments)
                {
                    fields.Add((arg as MemberExpression).Member.Name);
                }
        }

        return string.Join(",", fields);
    }
Carlos Bolivar
  • 307
  • 5
  • 12
4

I have done the INotifyPropertyChanged implementation similar to the method below. Here the properties are stored in a dictionary in the base class shown below. It is of course not always desirable to use inheritance, but for view models I think it is acceptable and gives very clean property references in the view model classes.

public class PhotoDetailsViewModel
    : PropertyChangedNotifierBase<PhotoDetailsViewModel>
{
    public bool IsLoading
    {
        get { return GetValue(x => x.IsLoading); }
        set { SetPropertyValue(x => x.IsLoading, value); }
    }

    public string PendingOperation
    {
        get { return GetValue(x => x.PendingOperation); }
        set { SetPropertyValue(x => x.PendingOperation, value); }
    }

    public PhotoViewModel Photo
    {
        get { return GetValue(x => x.Photo); }
        set { SetPropertyValue(x => x.Photo, value); }
    }
}

The somewhat more complex base class is shown below. It handles the translation from lambda expression to property name. Note that the properties are really pseudo properties since only the names are used. But it will appear transparent to the view model and references to the properties on the view model.

public class PropertyChangedNotifierBase<T> : INotifyPropertyChanged
{
    readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

    protected U GetValue<U>(Expression<Func<T, U>> property)
    {
        var propertyName = GetPropertyName(property);

        return GetValue<U>(propertyName);
    }

    private U GetValue<U>(string propertyName)
    {
        object value;

        if (!_properties.TryGetValue(propertyName, out value))
        {
            return default(U);
        }

        return (U)value;
    }

    protected void SetPropertyValue<U>(Expression<Func<T, U>> property, U value)
    {
        var propertyName = GetPropertyName(property);

        var oldValue = GetValue<U>(propertyName);

        if (Object.ReferenceEquals(oldValue, value))
        {
            return;
        }
        _properties[propertyName] = value;

        RaisePropertyChangedEvent(propertyName);
    }

    protected void RaisePropertyChangedEvent<U>(Expression<Func<T, U>> property)
    {
        var name = GetPropertyName(property);
        RaisePropertyChangedEvent(name);
    }

    protected void RaisePropertyChangedEvent(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private static string GetPropertyName<U>(Expression<Func<T, U>> property)
    {
        if (property == null)
        {
            throw new NullReferenceException("property");
        }

        var lambda = property as LambdaExpression;

        var memberAssignment = (MemberExpression) lambda.Body;
        return memberAssignment.Member.Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
faester
  • 14,886
  • 5
  • 45
  • 56
  • 1
    You're basically maintaining a property bag. Not bad, but those calls from getters and setters of model class is little easier like `public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }`. Could be slower, but more generic and straightforward. – nawfal Dec 18 '13 at 18:16
  • Actually implementing a simple dependency property system is harder ( but not soo hard ) but actually much more performant than the above implementation. – Felix K. Sep 20 '15 at 19:08
4

This is another answer:

public static string GetPropertyName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                      Expression<Func<TModel, TProperty>> expression)
    {
        var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        return metaData.PropertyName;
    }
Memo
  • 329
  • 3
  • 5
4

I created an extension method on ObjectStateEntry to be able to flag properties (of Entity Framework POCO classes) as modified in a type safe manner, since the default method only accepts a string. Here's my way of getting the name from the property:

public static void SetModifiedProperty<T>(this System.Data.Objects.ObjectStateEntry state, Expression<Func<T>> action)
{
    var body = (MemberExpression)action.Body;
    string propertyName = body.Member.Name;

    state.SetModifiedProperty(propertyName);
}
Anders
  • 734
  • 8
  • 12
3

Here is another way to get the PropertyInfo based off this answer. It eliminates the need for an object instance.

/// <summary>
/// Get metadata of property referenced by expression. Type constrained.
/// </summary>
public static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
{
    return GetPropertyInfo((LambdaExpression) propertyLambda);
}

/// <summary>
/// Get metadata of property referenced by expression.
/// </summary>
public static PropertyInfo GetPropertyInfo(LambdaExpression propertyLambda)
{
    // https://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression
    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if(propertyLambda.Parameters.Count() == 0)
        throw new ArgumentException(String.Format(
            "Expression '{0}' does not have any parameters. A property expression needs to have at least 1 parameter.",
            propertyLambda.ToString()));

    var type = propertyLambda.Parameters[0].Type;
    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(String.Format(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));
    return propInfo;
}

It can be called like so:

var propertyInfo = GetPropertyInfo((User u) => u.UserID);
Community
  • 1
  • 1
Hans Vonn
  • 3,949
  • 3
  • 21
  • 15
3

GetPropetyAccess() is available if you can refer efcore.

using Microsoft.EntityFrameworkCore.Infrastructure;

var propertyInfo = lambda.GetPropetyAccess(); //PropertyInfo
var propertyName = propertyInfo.Name;
Sygel4456
  • 84
  • 5
2

I've updated @Cameron's answer to include some safety checks against Convert typed lambda expressions:

PropertyInfo GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
  var body = propertyLambda.Body;
  if (!(body is MemberExpression member)
    && !(body is UnaryExpression unary
      && (member = unary.Operand as MemberExpression) != null))
    throw new ArgumentException($"Expression '{propertyLambda}' " +
      "does not refer to a property.");

  if (!(member.Member is PropertyInfo propInfo))
    throw new ArgumentException($"Expression '{propertyLambda}' " +
      "refers to a field, not a property.");

  var type = typeof(TSource);
  if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
    throw new ArgumentException($"Expresion '{propertyLambda}' " + 
      "refers to a property that is not from type '{type}'.");

  return propInfo;
}
Community
  • 1
  • 1
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
2

Starting with .NET 4.0 you can use ExpressionVisitor to find properties:

class ExprVisitor : ExpressionVisitor {
    public bool IsFound { get; private set; }
    public string MemberName { get; private set; }
    public Type MemberType { get; private set; }
    protected override Expression VisitMember(MemberExpression node) {
        if (!IsFound && node.Member.MemberType == MemberTypes.Property) {
            IsFound = true;
            MemberName = node.Member.Name;
            MemberType = node.Type;
        }
        return base.VisitMember(node);
    }
}

Here is how you use this visitor:

var visitor = new ExprVisitor();
visitor.Visit(expr);
if (visitor.IsFound) {
    Console.WriteLine("First property in the expression tree: Name={0}, Type={1}", visitor.MemberName, visitor.MemberType.FullName);
} else {
    Console.WriteLine("No properties found.");
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
2
static void Main(string[] args)
{
    var prop = GetPropertyInfo<MyDto>(_ => _.MyProperty);

    MyDto dto = new MyDto();
    dto.MyProperty = 666;

    var value = prop.GetValue(dto);
    // value == 666
}

class MyDto
{
    public int MyProperty { get; set; }
}

public static PropertyInfo GetPropertyInfo<TSource>(Expression<Func<TSource, object>> propertyLambda)
{
    Type type = typeof(TSource);

    var member = propertyLambda.Body as MemberExpression;
    if (member == null)
    {
        var unary = propertyLambda.Body as UnaryExpression;
        if (unary != null)
        {
            member = unary.Operand as MemberExpression;
        }
    }
    if (member == null)
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));
    }

    var propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));
    }

    if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(), type));
    }

    return propInfo;
}
Stas BZ
  • 1,184
  • 1
  • 17
  • 36
0

Assuming (TModel as class)

Expression<Func<TModel, TValue>> expression

retrieve the property's name with

expression.GetPropertyInfo().Name;

The extension function:

public static PropertyInfo GetPropertyInfo<TType, TReturn>(this Expression<Func<TType, TReturn>> property)
{
  LambdaExpression lambda = property;
  var memberExpression = lambda.Body is UnaryExpression expression
      ? (MemberExpression)expression.Operand
      : (MemberExpression)lambda.Body;

  return (PropertyInfo)memberExpression.Member;
}
HGMamaci
  • 1,339
  • 12
  • 20