Meh*_*taş 8 c# closures dynamic linq-expressions expressionvisitor
下面是我的问题的简单演示代码.
[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);
}
}
Run Code Online (Sandbox Code Playgroud)
TestParam
方法导致VisitConstant
在两个不同的路径上调用:
1. TestParam
- > Search
- >VisitConstant
在此执行路径中,传递给Search
方法的常量表达式(1L)是实常数值.在这里,一切都很好,断言按预期成功.当VisitConstant
通过第一路径调用node.Value.GetType()
是Int64
和它的.Value
IS 1L
.
2. TestParam
- > GetStudent
- > Search
- >VisitConstant
在此执行路径中,常量表达式(id:1L)GetStudent
作为参数使用并传递给Search
闭包内的方法.
问题
问题出在第二个执行路径上.当VisitConstant
经由第二路径调用node.Value.GetType()
是MyProject.Tests.ExpressionTests+<>c__DisplayClass0
与这个类有一个名为公共字段id
(同GetStudent
其具有的值的方法的自变量)1L
.
题
如何id
在第二条道路上获得价值?我知道闭包,DisplayClass
它是什么以及为什么它在编译时创建等等.我只对获得它的字段值感兴趣.我能想到的一件事是通过反思.有类似下面的东西,但它似乎并不整洁.
node.Value.GetType().GetFields()[0].GetValue(node.Value);
Run Code Online (Sandbox Code Playgroud)
奖金问题
在使用gettting id
值的代码时,我更改了VisitConstant
下面的方法(虽然这不会解决我的问题)并且得到一个例外,说"'object'不包含'id'的定义"
奖金问题
由于动态在运行时解决并DisplayClass
在编译时创建,为什么我们不能用dynamic
?访问它的字段?虽然下面的代码工作,我期望代码也可以工作.
var st = new {Id = 1L};
object o = st;
dynamic dy = o;
Assert.AreEqual(1L, dy.Id);
Run Code Online (Sandbox Code Playgroud)
VisitConstant
在这里不会有帮助,因为它接收一个编译器,ConstantExpression
该编译器使用私有匿名类的对象来存储 lambda 被关闭的值(The DisplayClassxxx
)
相反,我们应该重写VisitMember
方法并检查它MemberExpression
已经ConstantExpression
作为内部的方法Expression
。
这是几乎没有反思的工作测试。
[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();
}
}
Run Code Online (Sandbox Code Playgroud)
这是一篇文章,解释了如何执行此操作,并包含执行此操作的代码。基本上,您可以做的是创建一个仅表示该子表达式的表达式,将其编译为委托,然后执行该委托。(本文还解释了如何识别可以计算的子表达式,但我猜您对此不感兴趣。)
使用本文中的代码,将代码修改为以下内容即可:
private void Search<T>(Expression<Func<T, bool>> filter)
{
new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter));
}
Run Code Online (Sandbox Code Playgroud)
由于动态是在运行时解析并
DisplayClass
在编译时创建的,为什么我们不能使用 访问它的字段dynamic
?
因为那DisplayClass
是一个private
嵌套在里面的类ExpressionTests
,所以里面的代码MyExpressionVisitor
无法访问它的成员。
如果您MyExpressionVisitor
在里面创建一个嵌套类ExpressionTests
,dynamic
则会开始处理DisplayClass
.
匿名类型不会以这种方式运行,因为它们不会作为嵌套private
类型发出。