Отримання імені властивості з виразу лямбда


512

Чи є кращий спосіб отримати ім'я властивості при передачі через лямбда-вираз? Ось що я зараз маю.

напр.

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

Він працював, передаючи його як memberexpression лише тоді, коли властивість була рядком. тому що не всі властивості є рядками, я повинен був використовувати об'єкт, але тоді він поверне для них неекспресію.

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 Вам не потрібно робити весь запит за один крок - ви можете використовувати "regular / lambda" OrderBy, "динамічний LINQ / string" Де тощо.
Марк Гравелл

1
можливий дублікат get-property-name-name-type-using-lambda-
express

4
Примітка для всіх: Використовуйте MemberExpressionнаведений тут підхід лише для того, щоб отримати ім'я учасника, а не отримати власне фактичне MemberInfo, оскільки 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 рік, висновок типу працює нормально без вихідного параметра.
HappyNomad

4
@HappyNomad Уявіть собі об’єкт, у якого є член, екземпляр третього типу. u => u.OtherType.OtherTypesPropertyстворив би такий випадок, на який перевіряється остання заява.
joshperry

5
Останнє, якщо вислів має бути: 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

146

Я грав із тим самим і працював над цим. Він не повністю перевірений, але, здається, вирішує проблему з типами значень (проблема з неекспресією, з якою ви стикалися)

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 is UnaryExpression? ((UnaryExpression) field.Body) .Operand: 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 <Кошик> (m => m.Quantity)
Soren

1
@soren Я впевнений, що хтось більш налаштований, ніж я, може запропонувати вам відкрити код до потенційного непотрібного боксу / розблокування при передачі виразів типів значень, але оскільки вираз ніколи не компілюється та не оцінюється в цьому методі, це, мабуть, не проблема.
Пол Флемінг

29

З відповідності шаблону 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кращий випадок, коли мова йде про .Length. Хоча "Length" виставляється як властивість, ви не можете використовувати його в жодному із запропонованих раніше рішень.

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" буде надруковано на консоль (схоже, компілятор генерує прямий доступ до поля довжини резервної копії , як оптимізація навіть у налагодженні, таким чином, особливий випадок).


16

Ось оновлення методу, запропонованого Cameron . Перший параметр не обов’язковий.

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не захоплюють вкладені / підвластивості.

ex) 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" або виняток - тобто чи потрібно взагалі повторювати.

(повторний коментар) для отримання детальної інформації про гнучку сортування дивіться тут .


Так ... це єдиний предмет першого рівня, який використовується для створення посилань на сортування стовпців. напр. Якщо у мене є модель, і я хочу відобразити ім'я стовпця для сортування за я, я можу використовувати сильно набране посилання на об’єкт, щоб отримати ім’я властивості, для якого в динамічному linq не буде корова. ура.
Schotime

ToStringмає дати потворні результати для одинарних виразів.
nawfal

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

Я оновив відповідь @ Кемерона, щоб включити кілька перевірок безпеки щодо 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.