【问题标题】: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);

干杯。

【问题讨论】:

    标签: c# linq lambda expression-trees


    【解决方案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 - 确实如此。要以通用方式处理此问题,请参阅此处的 EvaluateTryEvaluatecode.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”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多