访问成员表达式的值


70

如果我有产品。

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

干杯。

Answers:


117

您可以编译和调用主体为成员访问权限的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在很多地方都可以做到这一点。


1
得到这个错误当它像我使用的示例一样解析为double时,不能将类型为System.Double的表达式用于返回类型System.Object。
Schotime

5
必须添加:var expression = Expression.Convert(member,typeof(object)); 在函数的第一行中使用双转换来修复上述错误!
Schotime

是的,我有时会忘记您必须对隐含C#的表达式树(例如转换)进行显式声明。我很高兴这对您有用。
布赖恩·瓦茨

太棒了!! :) 非常感谢。
sebastian

注意:如果您具有类型的无参数表达式 (例如包含表达式),则单线可解决问题。然后将其转换为所需的类型。在某些情况下很有用。exprExpression<Func<T>>expr() => myObjobject exprValue = expr.Compile()();
马特

35
 MemberExpression right = (MemberExpression)((BinaryExpression)p.Body).Right;
 Expression.Lambda(right).Compile().DynamicInvoke();

获得结果的最快,最简洁的方法。
iggymoran 2012年

1
不能相信的东西,包括DynamicInvoke可以最快@iggymoran你测试它?还是您想输入的最快?;)
nawfal 2013年

打字最快,最容易了解正在发生的事情。DynamicInvoke最终使用反射来执行它,这并不是世界上最快的事情。Bryan Watts的答案通过获得一个func并执行(仅通过调用)就解决了这个问题。当我第一次遇到这个答案时,很容易理解发生了什么。
iggymoran 2013年

如果可以的话,我会给你+10的:)太棒了
icesar 2013年

26

常量表达式将指向编译器生成的捕获类。我没有包括决策点等,但是这是从中获得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 2010年

3
@Schotime-确实可以。要在通用的方式处理这个问题,看EvaluateTryEvaluate这里:code.google.com/p/protobuf-net/source/browse/trunk/...
马克Gravell

@MarcGravell哪个更快:编译一个MemberExpression然后评估它,或者达到它PropertyInfo/FieldInfo然后评估它,如TryEvaluate
阿什拉夫·萨布里

1
@AshrafSabry取决于您执行了多少次,以及您是否重用了该代表
Marc Gravell

3

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

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

可以Compile()完全避免这些问题,也可以缓存已编译的委托以重新使用它们。一点我的图书馆同时提供解释Expressions,以及缓存的编译,其中表达的所有的常量和关闭自动附加参数,然后将它们重新插入到闭合,这是返回给用户被替换。这两个过程都经过了严格的测试,并在生产中使用,它们都有各自的优缺点,但比它们快100倍以上Compile()-并且避免了内存泄漏!


2

如果您上过课:

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”


1
value是保留的标识符
尼克加利莫尔

@NickGallimore谢谢,好地方!我已经相应地更新了示例,以删除此内容
t_warsop

1

q是类型List<Product>。列表没有价格属性-只有单个产品。

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

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

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

q.Single().Price

是的,但.ToList()最后将其列入列表。
Kirk Broadhurst,2010年

1
无论是列表还是IQueryable,您仍然可以使用“第一”,“最后”或“单个”-但是,毫无疑问,repo.Products.ToList()绝对是列表
Kirk Broadhurst,2010年

1
您是正确的Kobi。我确实了解语法树,所以我对这些东西一无所知。只是稍微复杂一点。
Schotime

好的,现在我明白了您正在/正在努力实现的目标,我从最初的问题中无法理解。
Kirk Broadhurst,2010年

1

您可以使用以下内容:

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

这将起作用,但是如果不需要这样做,那就太好了。Linq-2-Sql是否支持我尝试实现的语法?
Schotime 2010年

0

您到底想完成什么?

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

var valueOfPrice = q[0].Price;

我试图将表达式传递为纯文本,并且需要将p.Price评估为“ 30”
Schotime 2010年

-1

截至2020年

此辅助方法将优雅地检索任何表达式值,而无需“ compile hack”:

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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.