以下是我的问题的简单演示代码。
[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
方法导致在两个不同的路径上调用VisitConstant
:1.
TestParam
-> Search
-> VisitConstant
在此执行路径中,传递给
Search
方法的常量表达式(1L)是实际常量值。在这里,一切正常,断言按预期成功。通过第一个路径调用VisitConstant
时,node.Value.GetType()
是Int64
,而其.Value
是1L
。2.
TestParam
-> GetStudent
-> Search
-> VisitConstant
在此执行路径中,
GetStudent
将常量表达式(id:1L)作为参数,并在闭包内部传递给Search
方法。问题
问题出在第二条执行路径上。当通过第二条路径调用
VisitConstant
时,node.Value.GetType()
是MyProject.Tests.ExpressionTests+<>c__DisplayClass0
,并且此类有一个名为id
的公共(public)字段(与GetStudent
方法的参数相同),其值为1L
。问题
如何在第二条路径中获得
id
值?我知道闭包,什么是DisplayClass
以及为什么在编译时创建它等。我只对获取其字段值感兴趣。我能想到的一件事是,通过反射(reflection)。与下面类似的东西,但看起来并不整洁。
node.Value.GetType().GetFields()[0].GetValue(node.Value);
奖励问题
在使用获取
id
值的代码玩游戏时,我更改了VisitConstant
方法,如下所示(尽管这无法解决我的问题),但出现了一个异常,说明“'object'不包含'id'的定义”奖励问题
由于动力学是在运行时解析的,而
DisplayClass
是在编译时创建的,为什么我们不能使用dynamic
来访问其字段?虽然下面的代码有效,但我希望代码也能正常工作。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,并包含执行此操作的代码。基本上,您可以做的是创建一个仅表示该子表达式的表达式,将其编译为委托(delegate),然后执行该委托(delegate)。 (本文还介绍了如何识别可以评估的子表达式,但我想您对此并不感兴趣。)
使用本文中的代码,将代码修改为以下代码将起作用:
private void Search<T>(Expression<Func<T, bool>> filter)
{
new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter));
}
由于该
DisplayClass
是嵌套在private
内的ExpressionTests
类,因此MyExpressionVisitor
内的代码无法访问其成员。如果将
MyExpressionVisitor
设置为ExpressionTests
内的嵌套类,则dynamic
将开始在DisplayClass
上工作。匿名类型不会以这种方式运行,因为它们不会作为嵌套的
private
类型发出。