Getting ConstantExpression.Value when the actual value packed in DisplayClass due to closure

Below is a simple demo code of my problem.

[TestClass] public class ExpressionTests { [TestMethod] public void TestParam() { Search<Student>(s => s.Id == 1L); GetStudent(1L); } private void GetStudent(long id) { Search<Student>(s => s.Id == id); } private void Search<T>(Expression<Func<T, bool>> filter) { var visitor = new MyExpressionVisitor(); visitor.Visit(filter); } } public class MyExpressionVisitor : ExpressionVisitor { protected override Expression VisitConstant(ConstantExpression node) { Assert.AreEqual(1L, node.Value); return base.VisitConstant(node); } } 

TestParam calls VisitConstant on two different paths:

1. TestParam β†’ Search β†’ VisitConstant

In this execution path, the constant expression (1L) passed to the Search method is a real constant value. Everything is in order here, the statement succeeds as expected. When VisitConstant is called through the first path, node.Value.GetType() is Int64 and its .Value is 1L .

2. TestParam β†’ GetStudent β†’ Search β†’ VisitConstant

In this expression, the execution path constant (id: 1L) is taken as an argument to GetStudent and passed to the Search method inside the closure.

Problem

The problem is the second way of execution. When VisitConstant is called via the second path node.Value.GetType() is MyProject.Tests.ExpressionTests+<>c__DisplayClass0 , and this class has a public field called id (the same as the argument of the GetStudent method), which has a value of 1L .

Question

How can I get the id value in the second path? I know about closing what DisplayClass and why it is created at compile time, etc. I'm only interested in its field value. One thing I can think of is reflection. With something like below, but that doesn't seem neat.

 node.Value.GetType().GetFields()[0].GetValue(node.Value); 

Bonus problem

While playing with the code for gettting id , I changed the VisitConstant method as shown below (which will not solve my problem) and get an exception saying that the "object" does not contain a definition for "id"

enter image description here

Bonus Question

How dynamics is resolved at runtime, and DisplayClass is created at compile time, why can't we access our fields with dynamic ? Although the code works below, I expected the code to work as well.

 var st = new {Id = 1L}; object o = st; dynamic dy = o; Assert.AreEqual(1L, dy.Id); 
+7
closures c # dynamic linq-expressions
source share
2 answers

Here is an article that explains how to do this , and includes code that does this. Basically, you can create an expression that represents only this subexpression, compile it for the delegate, and then execute that delegate. (The article also explains how to identify subexpressions that can be evaluated, but I think you're not interested in that.)

Using the code from the article, your code will be changed to the following:

 private void Search<T>(Expression<Func<T, bool>> filter) { new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter)); } 

Since dynamics is allowed at runtime and a DisplayClass is created at compile time, why can't we access our fields with dynamic ?

Because DisplayClass is a private class nested inside ExpressionTests , so the code inside MyExpressionVisitor cannot access its members.

If you make MyExpressionVisitor nested class inside ExpressionTests , dynamic will start working with DisplayClass .

Anonymous types do not behave this way because they are not selected as nested private types.

+3
source share

VisitConstant won't help here, since it gets a built-in ConstantExpression compiler that uses a private anonymous class object to store lambda values ​​has been closed ( DisplayClassxxx )

Instead, we should override the VisitMember method and check its MemberExpression , which already has ConstantExpression as an internal Expression .

Here is a working test with little reflection.

 [TestClass] public class UnitTest2 { [TestMethod] public void TestMethod2() { Search<Student>(s => s.Id == 1L); GetStudent(1L); } private void GetStudent(long id) { Search<Student>(s => s.Id == id); } private void Search<T>(Expression<Func<T, bool>> filter) { var visitor = new MyExpressionVisitor2(); visitor.Visit(filter.Body); } } //ExpressionVisitor public class MyExpressionVisitor2 : ExpressionVisitor { protected override Expression VisitMember(MemberExpression node) { switch (node.Expression.NodeType) { case ExpressionType.Constant: case ExpressionType.MemberAccess: { var cleanNode = GetMemberConstant(node); //Test Assert.AreEqual(1L, cleanNode.Value); return cleanNode; } default: { return base.VisitMember(node); } } } private static ConstantExpression GetMemberConstant(MemberExpression node) { object value; if (node.Member.MemberType == MemberTypes.Field) { value = GetFieldValue(node); } else if (node.Member.MemberType == MemberTypes.Property) { value = GetPropertyValue(node); } else { throw new NotSupportedException(); } return Expression.Constant(value, node.Type); } private static object GetFieldValue(MemberExpression node) { var fieldInfo = (FieldInfo)node.Member; var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value; return fieldInfo.GetValue(instance); } private static object GetPropertyValue(MemberExpression node) { var propertyInfo = (PropertyInfo)node.Member; var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value; return propertyInfo.GetValue(instance, null); } private static ConstantExpression TryEvaluate(Expression expression) { if (expression.NodeType == ExpressionType.Constant) { return (ConstantExpression)expression; } throw new NotSupportedException(); } } 
+2
source share

All Articles