Получение имени свойства из лямбда-выражения


513

Есть ли лучший способ получить имя свойства при передаче через лямбда-выражение? Вот что у меня сейчас есть.

например.

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

Он работал, выражая его как выражение, только когда свойство было строкой. потому что не все свойства являются строками, я должен был использовать объект, но тогда он вернул бы унарное выражение для них.

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;
}

Лучше как в более хорошем коде? Я так не думаю. Проверка типов распространяется только на общее выражение, поэтому вам действительно нужны проверки, которые вы выполняете во время выполнения. :(
MichaelGG

Да ... просто было интересно, есть ли лучший способ сделать это, так как мне показалось немного хакерским. Но если это то круто. Спасибо.
Schotime

Я обновил ваш комментарий; но использование лямбды для получения строки, чтобы вы могли использовать динамический LINQ, показалось мне делом задом наперед ... если вы используете лямбду, используйте лямбду ;-p Вам не нужно выполнять весь запрос за один шаг - Вы могли бы использовать «обычный / лямбда» OrderBy, «динамический LINQ / строка», где и т. д.
Марк Гравелл

1
возможный дубликат get-property-name-name-and-type-using-lambda-expression
nawfal

4
Примечание для всех: используйте MemberExpressionподход, перечисленный здесь, только для получения имени члена, а не для получения MemberInfoсамого MemberInfoфакта , поскольку в определенных сценариях «dervied: base» гарантированный тип не гарантированно будет отраженного типа. См. Lambda-expression-not-returning-Ожидаемый-memberinfo . Споткнул меня однажды. Принятый ответ тоже страдает от этого.
Nawfal

Ответы:


350

Недавно я сделал очень похожую вещь, чтобы сделать безопасный метод типа OnPropertyChanged.

Вот метод, который возвращает объект PropertyInfo для выражения. Выдает исключение, если выражение не является свойством.

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    TSource source,
    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(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

sourceПараметр используется для того , компилятор может сделать вывод типа вызова метода. Вы можете сделать следующее

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);

6
Почему последняя проверка относительно TSource там? Лямбда строго напечатана, поэтому я не думаю, что это необходимо.
HappyNomad

16
Кроме того, с 2012 года вывод типов работает нормально без параметра source.
HappyNomad

4
@HappyNomad Представьте объект, который имеет в качестве члена экземпляр третьего типа. u => u.OtherType.OtherTypesPropertyсоздаст такой случай, что последний оператор проверяет.
Joshperry

5
Последний оператор if должен быть таким: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))разрешить интерфейсы тоже.
Грэм Кинг,

8
@GrayKing это не было бы так же, как просто if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Коннелл

192

Я нашел другой способ, которым вы могли бы сделать, это строго определить тип источника и свойства и явно сделать вывод для лямбда-выражения. Не уверен, что это правильная терминология, но вот результат.

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);
}

А потом назови это так.

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

и вуаля это работает.
Спасибо всем.


4
Это решение должно быть немного обновлено. Пожалуйста, проверьте следующую статью - вот ссылка
Павел Чермак

1
Это только вариант, если вы делаете ASP.Net MVC и только для уровня пользовательского интерфейса (HtmlHelper).
Марк

3
начиная с c # 6.0 вы можете использоватьGetInfo(nameof(u.UserId))
Владислав

1
В чистом ядре я должен был использовать это:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk

147

Я играл с тем же самым и работал над этим. Он не полностью протестирован, но, похоже, решает проблему с типами значений (проблема с одним выражением, с которой вы столкнулись)

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;
}

2
попробовал это недавно (из другого вопроса ), обнаружил, что он не обрабатывает подвойства: o => o.Thing1.Thing2вернется Thing2, нет Thing1.Thing2, что неправильно, если вы пытаетесь использовать его в EntityFramework включает в себя
drzaus

1
AKA (field.Body - это UnaryExpression? ((UnaryExpression) field.Body). Операнд: field.Body) в качестве MemberExpression

51
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

Это обрабатывает член и унарные выражения. Разница в том, что вы получите, UnaryExpressionесли ваше выражение представляет тип значения, тогда как вы получите, MemberExpressionесли ваше выражение представляет ссылочный тип. Все может быть приведено к объекту, но типы значений должны быть упакованы. Вот почему существует UnaryExpression. Ссылка.

Ради читабельности (@Jowen), вот расширенный эквивалент:

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;
}

@flem, я опускаю <TField> для удобства чтения, есть ли проблемы. LambdaExpressions.GetName <Basket> (m => m.Quantity)
Сорен

1
@soren Я уверен, что кто-то более настроенный, чем я, может предложить, что вы открываете свой код до возможности ненужной блокировки / распаковки при передаче выражений типов значений, но поскольку выражение никогда не компилируется и не оценивается в этом методе, это, вероятно, не проблема.
Пол Флеминг

30

С C # 7 сопоставления с образцом:

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());
    }
}

Пример:

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

[Обновление] C # 8 соответствия шаблону:

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())
    };


20

Это общая реализация для получения строкового имени полей / свойств / индексаторов / методов / методов расширения / делегатов структуры / класса / интерфейса / делегата / массива. Я проверил с комбинациями статических / экземпляра и неуниверсальных / универсальных вариантов.

//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);
}

Это тоже можно записать в простой whileцикл:

//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");
        }
    }
}

Мне нравится рекурсивный подход, хотя второй может быть легче читать. Это можно назвать так:

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();

распечатать последний член.

Замечания:

  1. В случае цепных выражений типа A.B.C«C» возвращается.

  2. Это не работает с consts, индексаторами массива или enums (невозможно охватить все случаи).


19

Есть крайний случай, когда дело доходит до. ArrayДлина. Хотя «Длина» выставляется как свойство, его нельзя использовать ни в одном из ранее предложенных решений.

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);
}

Теперь пример использования:

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

Если PropertyNameFromUnaryExprне проверять ArrayLength, «someArray» будет выводиться на консоль (кажется, что компилятор генерирует прямой доступ к полю Backing Length , как оптимизация, даже в Debug, таким образом, в особом случае).


16

Вот обновление метода, предложенного Кэмероном . Первый параметр не обязателен.

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;
}

Вы можете сделать следующее:

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

Методы расширения:

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;
}

Вы можете:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);

Нет, он не будет выводить uкак некоторый тип, он не может сделать это, потому что нет типа, чтобы выводить. Что вы можете сделать, этоGetPropertyInfo<SomeType>(u => u.UserID)
Лукас

14

Я обнаружил, что некоторые из предложенных ответов, которые углубляются в MemberExpression/ UnaryExpressionне охватывают вложенные / подвойства.

пр) o => o.Thing1.Thing2возвращается, Thing1а не Thing1.Thing2.

Это различие важно, если вы пытаетесь работать с EntityFramework DbSet.Include(...).

Я обнаружил, что просто анализ, Expression.ToString()кажется, работает нормально и сравнительно быстро. Я сравнил это с UnaryExpressionверсией, и даже вылез ToStringиз нее, Member/UnaryExpressionчтобы увидеть, было ли это быстрее, но разница была незначительной. Пожалуйста, поправьте меня, если это ужасная идея.

Метод продления

/// <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

(Проверка на разделитель может быть даже излишней)

Демо (LinqPad)

Демонстрация + код сравнения - https://gist.github.com/zaus/6992590


1
+1 очень интересно. Вы продолжали использовать этот метод в своем собственном коде? это работает нормально? Вы обнаружили какие-либо крайние случаи?
Бенджамин Гейл

Я не вижу твоей идеи. Судя по ответу вы связаны o => o.Thing1.Thing2не возвращается , Thing1как вы говорите , но Thing2. На самом деле ваш ответ возвращает что-то вроде того, Thing1.Thing2что может или не может быть желательным.
nawfal

Не работает с делом Корман предостерегает: stackoverflow.com/a/11006147/661933 . Всегда лучше избегать взломов.
nawfal

@nawfal # 1 - оригинальная проблема в том, что ты хочешь Thing1.Thing2 , никогда Thing1. Я сказал , Thing2имея в виду значение из o.Thing1.Thing2, которая является точкой предиката. Я обновлю ответ, чтобы отразить это намерение.
drzaus

@drzaus прости, я все еще не понимаю тебя. Искренне пытаюсь понять. Почему вы говорите, что другие ответы здесь возвращаются Thing1? Я не думаю, что это перенастраивает это вообще.
nawfal

6

Я использую метод расширения для проектов до C # 6 и nameof () для тех, кто ориентирован на 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;
    }
}

И я называю это так:

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);

Он отлично работает как с полями, так и со свойствами.


5

Ну, не нужно звонить .Name.ToString(), но в целом это все, да. Единственное, что вам может понадобиться, - это x.Foo.Barвозвращать ли "Foo", "Bar" или исключение - т.е. нужно ли вам вообще выполнять итерации.

(re comment) Подробнее о гибкой сортировке см. здесь .


Да ... это только вещь первого уровня, используемая для генерации ссылки на столбец сортировки. например. Если у меня есть модель, и я хочу отобразить имя столбца для сортировки, я могу использовать строго типизированную ссылку на объект, чтобы получить имя свойства, для которого у динамического linq не будет коровы. веселит.
Schotime

ToStringдолжен давать некрасивые результаты для унарных выражений.
ноября

3

Я создал метод расширения для ObjectStateEntry, чтобы иметь возможность помечать свойства (классов POCO Entity Framework) как измененные безопасным способом, поскольку метод по умолчанию принимает только строку. Вот мой способ получить имя от собственности:

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);
}

3

Я сделал INotifyPropertyChangedреализацию, похожую на метод ниже. Здесь свойства хранятся в словаре в базовом классе, показанном ниже. Конечно, не всегда желательно использовать наследование, но для моделей представлений я думаю, что это приемлемо и дает очень чистые ссылки на свойства в классах моделей представлений.

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); }
    }
}

Несколько более сложный базовый класс показан ниже. Он обрабатывает перевод из лямбда-выражения в имя свойства. Обратите внимание, что свойства действительно являются псевдо-свойствами, так как используются только имена. Но он будет прозрачным для модели представления и ссылок на свойства модели представления.

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;
}

1
Вы в основном поддерживаете собственность мешок. Неплохо, но эти вызовы от получателей и установщиков модельного класса немного проще public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Может быть медленнее, но более общим и простым.
Nawfal

На самом деле реализовать простую систему свойств зависимостей сложнее (но не слишком сложно), но на самом деле гораздо более производительно, чем приведенная выше реализация.
Феликс К.

3

Это еще один ответ:

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;
    }

1
ModelMetadataсуществует в System.Web.Mvcпространстве имен. Может быть, это не подходит для общего случая
asakura89

3

Я оставляю эту функцию, если вы хотите получить несколько полей:

/// <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);
    }

3
Собираетесь ли вы объяснить это?

1

Вот еще один способ получить PropertyInfo на основе этого ответа. Это устраняет необходимость в экземпляре объекта.

/// <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)
{
    // /programming/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;
}

Это можно назвать так:

var propertyInfo = GetPropertyInfo((User u) => u.UserID);

1

Я обновил ответ @ Cameron, включив в него некоторые проверки безопасности для Convertнапечатанных лямбда-выражений:

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;
}

1

Начиная с .NET 4.0 вы можете использовать ExpressionVisitorдля поиска свойств:

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);
    }
}

Вот как вы используете этого посетителя:

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.");
}

1

Это может быть оптимальным

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;
}

0
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;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.