Access the value of a member expression

2019-01-12 23:18发布

问题:

If i have a product.

var p = new Product { Price = 30 };

and i have the following linq query.

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

In an IQueryable provider, I get a MemberExpression back for the p.Price which contains a Constant Expression, however I can't seem to get the value "30" back from it.

Update I have tried this but it doesn't seem to work.

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

Cheers.

回答1:

You can compile and invoke a lambda expression whose body is the member access:

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

Local evaluation is a common technique when parsing expression trees. LINQ to SQL does this exact thing in quite a few places.



回答2:

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


回答3:

The constant expression is going to point to a capture-class generated by the compiler. I've not included the decision points etc, but here's how to get 30 from that:

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 is now 30. Note that I'm assuming that Price is a property, but in reality you would write a GetValue method that handles property / field.



回答4:

q is of type List<Product>. The List doesn't have a Price property - only the individual Products.

The first or last Product will have a price.

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

If you know there's only one in the collection you can also flatten it using Single

q.Single().Price


回答5:

Can you use the following:

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


回答6:

Using Expression.Lambda(myParameterlessExpression).Compile().Invoke() has several drawbacks:

  • .Compile() is slow. It can take multiple milliseconds to complete even for small expression fragments. The Invoke-call is super-fast afterwards though, takes only few nanoseconds for simple arithmetic expressions or member accesses.
  • .Compile() will generate (emit) MSIL code. That might sound perfect (and explains the excellent execution speed) but the problem is: That code takes up memory, which can not be freed before the application finishes, even when the GC collected the delegate-reference!

One can either avoid Compile() altogether to avoid these issues or cache the compiled delegates for re-using them. This little library of mine offers both interpretation of Expressions as well as cached compilation, where all constants and closures of the expression get replaced by additional parameters automatically, which are then re-inserted in a closure, which is returned to the user. Both processes are well-tested, used in production, both have their pros and cons against each other but are well over 100x faster than Compile() - and avoid the memory leak!



回答7:

And what exactly are you trying to accomplish?

Because to access the value of Price, you'd have to do something like:

var valueOfPrice = q[0].Price;


回答8:

If you had a class:

public class Item
{
    public int Id { get; set; }
}

and an instance of the object:

var myItem = new Item { Id = 7 };

You can get the value of Id using an Expression using the following code:

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

value will contain "7"