访问成员表达式的值

Posted

技术标签:

【中文标题】访问成员表达式的值【英文标题】:Access the value of a member expression 【发布时间】:2011-02-06 16:21:02 【问题描述】:

如果我有产品。

var p = new Product  Price = 30 ;

我有以下 linq 查询。

var q = repo.Products().Where(x=>x.Price == p.Price).ToList()

在 IQueryable 提供程序中,我为 p.Price 返回了一个 MemberExpression,其中包含一个常量表达式,但是我似乎无法从中获取值“30”。

更新 我已经尝试过,但它似乎不起作用。

var memberExpression = (MemberExpression)GetRootConstantExpression(m);
var fi = (PropertyInfo)memberExpression.Member;
var val = fi.GetValue(((ConstantExpression)memberExpression.Expression).Value, null);

干杯。

【问题讨论】:

【参考方案1】:

您可以编译和调用一个 lambda 表达式,其主体是成员访问:

private object GetValue(MemberExpression member)

    var objectMember = Expression.Convert(member, typeof(object));

    var getterLambda = Expression.Lambda<Func<object>>(objectMember);

    var getter = getterLambda.Compile();

    return getter();

局部求值是解析表达式树时的常用技术。 LINQ to SQL 在很多地方都可以做到这一点。

【讨论】:

Get this error Expression of type 'System.Double' cannot be used for return type 'System.Object' when it resolve to a double as in the example. 必须添加: var expression = Expression.Convert(member, typeof(object));在函数的第一行通过双重转换修复上述错误! 啊,是的,我有时会忘记,在 C# 是隐式的(如转换)的情况下,您必须对表达式树进行显式处理。我很高兴这对你有用。 太棒了! :) 非常感谢。 注意:如果您有一个 无参数表达式 expr 类型为 Expression&lt;Func&lt;T&gt;&gt;(例如 expr 包含表达式 @987654325 @),然后单线 object exprValue = expr.Compile()(); 就可以了。之后将其转换为您需要的类型。在某些情况下很有用。【参考方案2】:
 MemberExpression right = (MemberExpression)((BinaryExpression)p.Body).Right;
 Expression.Lambda(right).Compile().DynamicInvoke();

【讨论】:

获得结果的最快、最简洁的方式。 不敢相信涉及DynamicInvoke 的东西可能是最快 @iggymoran 你测试了吗?或者你的意思是打字最快? ;) 打字速度最快,最容易理解到底发生了什么。 DynamicInvoke 最终使用反射来执行它,并不是世界上最快的东西。 Bryan Watts 的回答通过获取一个函数并执行它(只需调用)来解决这个问题。当我第一次想到这个答案时,更容易理解发生了什么。 如果可以的话,我会给你+10 :) 太棒了【参考方案3】:

常量表达式将指向编译器生成的捕获类。我没有包括决策点等,但这里是如何从中获得 30:

var p = new Product  Price = 30 ;
Expression<Func<Product, bool>> predicate = x => x.Price == p.Price;
BinaryExpression eq = (BinaryExpression)predicate.Body;
MemberExpression productToPrice = (MemberExpression)eq.Right;
MemberExpression captureToProduct = (MemberExpression)productToPrice.Expression;
ConstantExpression captureConst = (ConstantExpression)captureToProduct.Expression;
object product = ((FieldInfo)captureToProduct.Member).GetValue(captureConst.Value);
object price = ((PropertyInfo)productToPrice.Member).GetValue(product, null);

price 现在是 30。请注意,我假设Price 是一个属性,但实际上您会编写一个处理属性/字段的GetValue 方法。

【讨论】:

如果你在对象中有另一个层次的嵌套,会有什么改变吗?例如。 p.Product.Price @Schotime - 确实如此。要以通用方式处理此问题,请参阅此处的 EvaluateTryEvaluate:code.google.com/p/protobuf-net/source/browse/trunk/… @MarcGravell 哪个更快:编译MemberExpression 然后评估它,或者达到它的PropertyInfo/FieldInfo 然后像TryEvaluate 一样评估它? @AshrafSabry 这取决于您执行了多少次,以及您是否正在重用委托【参考方案4】:

如果你有课:

public class Item

    public int Id  get; set; 

以及对象的一个​​实例:

var myItem = new Item  Id = 7 ;

您可以使用以下代码使用表达式获取 Id 的值:

Expression<Func<Item, int>> exp = x => x.Id;
var me = exp.Body as MemberExpression;
var propInfo = me.Member as PropertyInfo;
var myValue = propInfo.GetValue(myItem, null);

myValue 将包含“7”

【讨论】:

value 是保留标识符 @NickGallimore 谢谢,好地方!我已经相应地更新了示例以删除它【参考方案5】:

使用Expression.Lambda(myParameterlessExpression).Compile().Invoke() 有几个缺点:

.Compile()。即使是小的表达式片段也可能需要几毫秒才能完成。 Invoke-调用之后的速度非常快,简单的算术表达式或成员访问只需几纳秒。 .Compile() 将生成(发出)MSIL 代码。这听起来可能很完美(并解释了出色的执行速度),但问题是:该代码占用了内存,在应用程序完成之前无法释放,即使 GC 收集了委托引用!

可以完全避免Compile() 以避免这些问题,也可以缓存已编译的委托以重新使用它们。 This 我的小库同时提供Expressions解释以及缓存编译,其中表达式的所有常量和闭包都会自动替换为附加参数,这然后重新插入到一个闭包中,该闭包返回给用户。这两个过程都经过充分测试,用于生产,各有利弊,但比 Compile() 快 100 倍以上 - 并避免内存泄漏!

【讨论】:

已编译的委托和相关代码被垃圾回收【参考方案6】:

截至 2020 年

此帮助方法将优雅地检索任何表达式值,而无需“编译黑客”:

public static object GetMemberExpressionValue (MemberExpression expression)

    // Dependency chain of a MemberExpression is of the form:
    // MemberExpression expression
    //    MemberExpression expression.Expression
    //        ... MemberExpression expression.[...].Expression
    //            ConstantExpression expression.[...].Expression.Expression <- base object
    var dependencyChain = new List<MemberExpression>();
    var pointingExpression = expression;
    while (pointingExpression != null)
    
        dependencyChain.Add(pointingExpression);
        pointingExpression = pointingExpression.Expression as MemberExpression;
    

    if (!(dependencyChain.Last().Expression is ConstantExpression baseExpression))
    
        throw new Exception(
            $"Last expression dependencyChain.Last().Expression of dependency chain of expression is not a constant." +
            "Thus the expression value cannot be found.");
    

    var resolvedValue = baseExpression.Value;

    for (var i = dependencyChain.Count; i > 0; i--)
    
        var expr = dependencyChain[i - 1];
        resolvedValue = new PropOrField(expr.Member).GetValue(resolvedValue);
    

    return resolvedValue;

PropOrField 类:

public class PropOrField

    public readonly MemberInfo MemberInfo;

    public PropOrField (MemberInfo memberInfo)
    
        if (!(memberInfo is PropertyInfo) && !(memberInfo is FieldInfo))
        
            throw new Exception(
                $"nameof(memberInfo) must either be nameof(PropertyInfo) or nameof(FieldInfo)");
        

        MemberInfo = memberInfo;
    

    public object GetValue (object source)
    
        if (MemberInfo is PropertyInfo propertyInfo) return propertyInfo.GetValue(source);
        if (MemberInfo is FieldInfo fieldInfo) return fieldInfo.GetValue(source);

        return null;
    

    public void SetValue (object target, object source)
    
        if (MemberInfo is PropertyInfo propertyInfo) propertyInfo.SetValue(target, source);
        if (MemberInfo is FieldInfo fieldInfo) fieldInfo.SetValue(target, source);
    

    public Type GetMemberType ()
    
        if (MemberInfo is PropertyInfo propertyInfo) return propertyInfo.PropertyType;
        if (MemberInfo is FieldInfo fieldInfo) return fieldInfo.FieldType;

        return null;
    

【讨论】:

【参考方案7】:

q 的类型为 List&lt;Product&gt;。该列表没有 Price 属性 - 只有单个产品。

第一个或最后一个产品会有一个价格。

q.First().Price
q.Last().Price

如果您知道集合中只有一个,您也可以使用 Single 将其展平

q.Single().Price

【讨论】:

是的,但最后的.ToList() 将其放入列表中。 无论是 List 还是 IQueryable,您仍然可以使用 First、Last 或 Single - 但不要搞错,repo.Products.ToList() 绝对是 List 你是对的科比。我对这些东西了如指掌,因为 I 确实在尝试解析表达式树。只是稍微复杂一点。 好的,我现在看到你是/想要达到的目标,我从最初的问题中不明白。【参考方案8】:

您可以使用以下内容吗:

var price = p.Price;
var q = repo.Products().Where(x=>x.Price == price).ToList()

【讨论】:

这会起作用,但是如果这不需要发生,那就太好了。 Linq-2-Sql 是否支持我想要实现的语法?【参考方案9】:

你到底想完成什么?

因为要访问Price 的值,您必须执行以下操作:

var valueOfPrice = q[0].Price;

【讨论】:

我正在尝试将表达式转换为纯文本,并且需要将 p.Price 评估为“30”

以上是关于访问成员表达式的值的主要内容,如果未能解决你的问题,请参考以下文章

常量表达式中的静态成员访问

Static在类中的作用

类成员访问:第 3.4.5 节,第 2 点:来自 N3290 草案 C++

MVEL 与反射。关于对象成员访问通常更快?

如何在 lambda 表达式中捕获单个类数据成员?

关于lambda表达式的两点使用