从lambda表达式中获取属性名称

598

有没有更好的方法在通过lambda表达式传递属性名时获取属性名?以下是我目前拥有的代码。

例如:

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

2
我已经根据您的评论进行了更新;但是使用lambda获取字符串以便使用动态LINQ似乎是在反其道而行之...如果您使用lambda,请继续使用lambda ;-p 您不必一步完成整个查询 - 您可以使用“常规/lambda” OrderBy,“动态LINQ/string” Where等。 - Marc Gravell
2
可能是 get-property-name-and-type-using-lambda-expression 的重复问题。 - nawfal
5
з»ҷеӨ§е®¶дёҖдёӘжҸҗзӨәпјҡеҸӘдҪҝз”ЁжӯӨеӨ„еҲ—еҮәзҡ„MemberExpressionж–№жі•иҺ·еҸ–жҲҗе‘ҳзҡ„еҗҚз§°пјҢиҖҢдёҚжҳҜиҺ·еҸ–е®һйҷ…зҡ„MemberInfoжң¬иә«пјҢеӣ дёәеңЁжҹҗдәӣвҖңжҙҫз”ҹ:еҹәзЎҖвҖқеңәжҷҜдёӯпјҢиҝ”еӣһзҡ„MemberInfoдёҚиғҪдҝқиҜҒжҳҜеҸҚе°„зұ»еһӢгҖӮиҜҰи§Ғlambda-expression-not-returning-expected-memberinfoгҖӮжӣҫз»Ҹи®©жҲ‘зҠҜдәҶй”ҷгҖӮжҺҘеҸ—зҡ„зӯ”жЎҲд№ҹеӯҳеңЁжӯӨй—®йўҳгҖӮ - nawfal
1
@nawfal 猜测你的意思是“派生”。 - George Birbilis
4
从C# 6开始,您可以直接使用nameof(),例如:nameof(User.UserId)。不需要任何辅助方法,它在编译时被替换! - S.Serpooshan
显示剩余3条评论
23个回答

7

我正在为早于C# 6的项目使用扩展方法,对于那些针对C# 6的项目,则使用nameof() 方法。

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

它可以很好地处理字段和属性。


7

这可能是最优的选择

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

好的,没有必要调用.Name.ToString(),但总的来说就是这样了。唯一需要考虑的是x.Foo.Bar应该返回"Foo"、"Bar"还是异常——也就是说,你是否需要迭代。

(关于评论)有关灵活排序的更多信息,请参见此处


1
是的...这只是一个一级的东西,用于生成排序列链接。例如,如果我有一个模型并且想要显示按列名排序,我可以使用强类型链接到对象来获取属性名称,这样动态linq就不会出问题了。谢谢。 - Schotime
ToString对于一元表达式的结果会很丑陋。 - nawfal

5

如果您想获取多个字段,我会离开这个功能:

/// <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
你打算解释这个吗? - user7627726

4
这是另一个答案:
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

4

我创建了一个基于ObjectStateEntry的扩展方法,可以以类型安全的方式标记实体框架POCO类的属性为修改状态,因为默认方法只接受字符串。下面是我获取属性名称的方式:

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

4

我已经按照以下方法实现了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); }
    }
}

下面是稍微复杂一些的基类。它处理从Lambda表达式到属性名的翻译。请注意,这些属性实际上是伪属性,因为仅使用名称。但对于视图模型和对视图模型上属性的引用来说,它们将透明。

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
你基本上在维护一个属性包。这不错,但是模型类的getter和setter调用有点更容易,例如public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }。可能会慢一些,但更通用和直接。 - nawfal
实际上,实现一个简单的依赖属性系统比上述实现更具性能,虽然难度较大(但并不是非常困难)。 - Felix K.

3
如果您可以引用 efcore,那么可以使用 GetPropetyAccess()
using Microsoft.EntityFrameworkCore.Infrastructure;

var propertyInfo = lambda.GetPropetyAccess(); //PropertyInfo
var propertyName = propertyInfo.Name;

1
对于使用.NET Core 2.1的用户,我发现可以通过导入Microsoft.EntityFrameworkCore.Internal来找到该方法。 - fdrobidoux

3

这里有另一种获取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)
{
    // https://dev59.com/-HRB5IYBdhLWcg3wSVYI
    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);

2

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接