当前位置:  开发笔记 > 编程语言 > 正文

从lambda表达式中检索属性名称

如何解决《从lambda表达式中检索属性名称》经验,为你挑选了12个好方法。

通过lambda表达式传入时,是否有更好的方法来获取属性名称?这是我现在拥有的.

例如.

GetSortingInfo(u => u.UserId);

只有当属性是字符串时,它才能将其作为元素表达式进行处理.因为不是所有属性都是字符串我必须使用对象,但它会返回一个单一表达式.

public static RouteValueDictionary GetInfo(this HtmlHelper html, 
    Expression> 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;
}

Cameron MacF.. 333

我最近做了一个非常类似的事情来制作一个类型安全的OnPropertyChanged方法.

这是一个方法,它将返回表达式的PropertyInfo对象.如果表达式不是属性,则抛出异常.

public PropertyInfo GetPropertyInfo(
    TSource source,
    Expression> 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);

此外,从2012年开始,类型推断在没有source参数的情况下工作正常. (16认同)

反思(没有双关语)是的,你是对的. (11认同)

@GrayKing不会和`if(!propInfo.ReflectedType.IsAssignableFrom(type))`一样吗? (8认同)

为什么在那里有关于TSource的最后一次检查?lambda是强类型的,所以我不认为这是必要的. (6认同)

最后一个if语句应该是:`if(type!= propInfo.ReflectedType &&!type.IsSubclassOf(propInfo.ReflectedType)&&!propInfo.ReflectedType.IsAssignableFrom(type))`以允许接口. (5认同)

@HappyNomad想象一个对象,它有一个成员,一个第三种类型的实例.`u => u.OtherType.OtherTypesProperty`会创建最后一个语句正在检查的情况. (4认同)

ToString()在异常消息格式化中是多余的 (4认同)

我们可以更改签名以将类的类型作为第一个参数吗?然后我们可能不需要创建'someUserObject'而是发送typeof(userclass).那可能吗? (2认同)


Schotime.. 186

我发现你可以做的另一种方法是强烈输入源和属性,并明确推断lambda的输入.不确定这是否是正确的术语,但这是结果.

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

    return GetInfo(html, name);
}

然后这样称呼它.

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

并且它有效.
谢谢大家.



1> Cameron MacF..:

我最近做了一个非常类似的事情来制作一个类型安全的OnPropertyChanged方法.

这是一个方法,它将返回表达式的PropertyInfo对象.如果表达式不是属性,则抛出异常.

public PropertyInfo GetPropertyInfo(
    TSource source,
    Expression> 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);


此外,从2012年开始,类型推断在没有source参数的情况下工作正常.
反思(没有双关语)是的,你是对的.
@GrayKing不会和`if(!propInfo.ReflectedType.IsAssignableFrom(type))`一样吗?
为什么在那里有关于TSource的最后一次检查?lambda是强类型的,所以我不认为这是必要的.
最后一个if语句应该是:`if(type!= propInfo.ReflectedType &&!type.IsSubclassOf(propInfo.ReflectedType)&&!propInfo.ReflectedType.IsAssignableFrom(type))`以允许接口.
@HappyNomad想象一个对象,它有一个成员,一个第三种类型的实例.`u => u.OtherType.OtherTypesProperty`会创建最后一个语句正在检查的情况.
ToString()在异常消息格式化中是多余的
我们可以更改签名以将类的类型作为第一个参数吗?然后我们可能不需要创建'someUserObject'而是发送typeof(userclass).那可能吗?

2> Schotime..:

我发现你可以做的另一种方法是强烈输入源和属性,并明确推断lambda的输入.不确定这是否是正确的术语,但这是结果.

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

    return GetInfo(html, name);
}

然后这样称呼它.

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

并且它有效.
谢谢大家.


这个解决方案应该稍微更新一下.请查看以下文章 - 这是[**link**](http://stackoverflow.com/questions/6658669/lambda-expression-not-returning-expected-memberinfo)
从c#6.0开始,你可以使用`GetInfo(nameof(u.UserId))`

3> M Thelen..:

我正在玩同样的事情,并努力工作.它没有完全测试,但似乎处理值类型的问题(你遇到的unaryexpression问题)

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

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

    return body.Member.Name;
}


抓住一元条件的好工作...... +1
最近尝试了这个(来自[另一个问题](http://stackoverflow.com/questions/13588022/exclude-property-from-serialization-via-custom-attribute-json-net/16647343#16647343)),发现它确实如此不处理子属性:`o => o.Thing1.Thing2`将返回`Thing2`,而不是`Thing1.Thing2`,如果你试图在EntityFramework中使用它,这是不正确的
+1:这对我有用,是我需要的最简单的解决方案.谢谢发帖.

4> Paul Fleming..:
public string GetName(Expression> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

这会处理成员和一元表达式.不同之处在于,UnaryExpression如果表达式表示值类型,则将得到a,而MemberExpression如果表达式表示引用类型,则将得到a .可以将所有内容强制转换为对象,但必须将值类型装箱.这就是UnaryExpression存在的原因.参考.

对于可读性(@Jowen),这是一个扩展的等价物:

public string GetName(Expression> 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;
}



5> akhansari..:

使用C#7模式匹配:

public static string GetMemberName(this Expression 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(this HtmlHelper html, 
    Expression> action) where T : class
{
    var name = action.GetMemberName();
    return GetInfo(html, name);
}



6> Maslow..:

现在在C#6,你可以简单地使用nameof这样nameof(User.UserId)

这有很多好处,其中包括在编译时完成,而不是运行时.

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



7> kornman00..:

对于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(Exprs.Expression> expr)
{
    Contract.Requires(expr != null);
    Contract.Requires(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);

    return PropertyNameFromLambdaExpr(expr);
}

public static string PropertyNameFromExpr(Exprs.Expression> expr)
{
    Contract.Requires(expr != null);
    Contract.Requires(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中,因此特殊情况).



8> nawfal..:

这是获取struct/class/interface/delegate/array的fields/properties/indexers/methods/extension methods/delegates的字符串名称的一般实现.我已经使用静态/实例和非泛型/通用变体的组合进行了测试.

//involves recursion
public static string GetMemberName(this LambdaExpression memberSelector)
{
    Func 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();

打印最后一个成员.

注意:

    在链式表达式的情况下,A.B.C返回"C".

    这不适用于consts,数组索引器或enums(不可能涵盖所有情况).



9> Adrian..:

这是Cameron提出的方法的更新.第一个参数不是必需的.

public PropertyInfo GetPropertyInfo(
    Expression> 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(u => u.UserID);
var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);

扩展方法:

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

public static string NameOfProperty(this TSource source,
    Expression> 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);



10> drzaus..:

我发现一些建议的答案深入到MemberExpression/ UnaryExpression不捕获嵌套/子属性.

ex)o => o.Thing1.Thing2返回Thing1而不是Thing1.Thing2.

如果您尝试使用EntityFramework,这种区别很重要DbSet.Include(...).

我发现只是解析它Expression.ToString()似乎工作正常,而且相对较快.我将它与UnaryExpression版本进行了比较,甚至ToString离开了Member/UnaryExpression,看看它是否更快,但差别可以忽略不计.如果这是一个可怕的想法,请纠正我.

扩展方法

/// 
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas.  Technique @via /sf/ask/17360801/
/// 
/// Cheats and uses the tostring output -- Should consult performance differences
/// the model type to extract property names
/// the value type of the expected property
/// expression that just selects a model property to be turned into a string
/// Expression toString delimiter to split from lambda param
/// Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end
/// indicated property name
public static string GetPropertyName(this Expression> 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



11> kalitsov..:

我对C#6前项目使用扩展方法,对C#6使用名称().

public static class MiscExtentions
{
    public static string NameOf(this object @object, Expression> 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);

它适用于字段和属性.



12> Marc Gravell..:

嗯,没有必要打电话.Name.ToString(),但广泛的是关于它,是的.您可能需要考虑的唯一因素是是否x.Foo.Bar应该返回"Foo","Bar"或异常 - 即您是否需要迭代.

(重新评论)有关灵活排序的更多信息,请参阅此处.

推荐阅读
mobiledu2402852357
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有