Чтение лямбда-выражения [дубликат]

Так как это самый популярный вопрос в отношении этой проблемы, я снова опубликую здесь еще один ответ:

Похоже, что есть более простой способ сделать это (но только в MySQL):

select *
from (select * from mytable order by id, rev desc ) x
group by id

Пожалуйста, прокомментируйте ответ пользователя Bohemian в на этот вопрос за предоставление такого краткого и элегантного ответа на эту проблему.

EDIT: хотя это решение работает для многих людей, оно может быть нестабильным в долгосрочной перспективе, поскольку MySQL не гарантирует, что оператор GROUP BY вернет значимые значения для столбцов, не входящих в список GROUP BY. Поэтому используйте это решение на свой страх и риск

438
задан casperOne 5 January 2011 в 17:18
поделиться

12 ответов

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

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

и voila он работает. Спасибо всем.

162
ответ дан Mark Hurd 26 August 2018 в 15:10
поделиться

Я создал метод расширения объекта 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
ответ дан Anders 26 August 2018 в 15:10
поделиться

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

/// <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);
    }
2
ответ дан Carlos Bolivar 26 August 2018 в 15:10
поделиться

Я обновил @ ответ Кэмерона , чтобы включить некоторые проверки безопасности против введенных лямбда-выражений 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;
}
45
ответ дан Community 26 August 2018 в 15:10
поделиться

Начиная с .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
ответ дан dasblinkenlight 26 August 2018 в 15:10
поделиться

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

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;
}
133
ответ дан joshperry 26 August 2018 в 15:10
поделиться

Я использую метод расширения для проектов pre 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
ответ дан Kalitsov 26 August 2018 в 15:10
поделиться

В случае Array .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» будет печататься на консоль (компилятор, похоже, создает прямой доступ к полю Length для поддержки , как оптимизацию, даже в Debug, специальный случай).

19
ответ дан kornman00 26 August 2018 в 15:10
поделиться

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

(re comment) для более подробной информации о гибкой сортировке , см. здесь .

5
ответ дан Marc Gravell 26 August 2018 в 15:10
поделиться

Недавно я сделал очень схожую вещь, чтобы создать безопасный метод 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);
301
ответ дан Massimiliano Kraus 26 August 2018 в 15:10
поделиться

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

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;
    }
3
ответ дан Memo 26 August 2018 в 15:10
поделиться

Я выполнил реализацию 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;
}
17
ответ дан nawfal 26 August 2018 в 15:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: