Below is a simple demonstration 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
method causes VisitConstant
to be invoked on two different paths:
1. TestParam
-> Search
-> VisitConstant
In this execution path constant expression (1L) passed to Search
method is a real constant value. Here, everything is OK, assert succeeds as expected. When VisitConstant
is invoked via first path node.Value.GetType()
is Int64
and its .Value
is 1L
.
2. TestParam
-> GetStudent
-> Search
-> VisitConstant
In this execution path constant expression (id: 1L), is taken by GetStudent
as an argument and passed to Search
method inside a closure.
Problem
The problem is on the second execution path. When VisitConstant
is invoked via second path node.Value.GetType()
is MyProject.Tests.ExpressionTests+<>c__DisplayClass0
and this class has a public field named id
(same as GetStudent
method's argument) which has the value of 1L
.
Question
How can I get id
value in second path? I know about closures, what a DisplayClass
is and why it is created at compile time etc. I am only interested in getting its field value. One thing I can think of is, via reflection. With something like below but it does not seem neat.
node.Value.GetType().GetFields()[0].GetValue(node.Value);
Bonus Problem
While playing with the code for gettting id
value I changed VisitConstant
method like below (which will not solve my problem though) and get an exception saying "'object' does not contain a definition for 'id'"
Bonus Question
As dynamics are resolved at runtime and DisplayClass
is created at compile time, why cannot we access its fields with dynamic
? While below code works, I expected that code would work too.
var st = new {Id = 1L};
object o = st;
dynamic dy = o;
Assert.AreEqual(1L, dy.Id);
Here is an article that explains how to do it , and includes code that does it. Basically, what you can do is to create an expression that represents just that subexpression, compile it to a delegate and then execute that delegate. (The article also explains how to identify subexpressions that can be evaluated, but I guess you're not interested in that.)
Using the code from the article, modifying your code to the following will work:
private void Search<T>(Expression<Func<T, bool>> filter)
{
new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter));
}
As dynamics are resolved at runtime and
DisplayClass
is created at compile time, why cannot we access its fields withdynamic
?
Because that DisplayClass
is a private
class nested inside ExpressionTests
, so code inside MyExpressionVisitor
can't access its members.
If you make MyExpressionVisitor
a nested class inside ExpressionTests
, dynamic
will start working on the DisplayClass
.
Anonymous types don't behave this way, because they are not emitted as nested private
types.
VisitConstant
will not help here, as it receives a compiler constructed ConstantExpression
that uses object of private anonymous class to store values lambda was closed over (The DisplayClassxxx
)
Instead, we should override VisitMember
method and inspect its MemberExpression
that already has ConstantExpression
as an inner Expression
.
Here is 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();
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.