我创建了一个覆盖VisitConstant的ExpressionVisitor实现。但是,当我创建一个利用局部变量的表达式时,我似乎无法获得变量的实际值。
public class Person
{
public string FirstName { get; set; }
}
string name = "Michael";
Expression<Func<Person, object>> exp = p => p.FirstName == name;
我如何在ConstantExpression中获取变量“name”的值? 我唯一能想到的是:
string fieldValue = value.GetType().GetFields().First().GetValue(value).ToString();
显然,这并不适合非常灵活......
稍微复杂的例子如下:
Person localPerson = new Person { FirstName = "Michael" };
Expression<Func<Person, object>> exp = p => p.FirstName == localPerson.FirstName;
答案 0 :(得分:31)
编辑:好的,由于AHM的评论,现在你的意思更清楚了。
基本上,代码被编译为在单独的类中捕获name
- 然后应用字段访问以从引用它的实例的常量表达式获取其值。 (必须这样做,因为您可以在创建表达式后更改name
的值 - 但表达式会捕获变量,而不是值。)
因此,您实际上并不想对ConstantExpression
中的VisitConstant
执行任何操作 - 您希望在VisitMember
中处理字段访问。您需要从ConstantExpression
子项中获取值,然后将其提供给FieldInfo
以获取值:
using System;
using System.Linq.Expressions;
using System.Reflection;
public class Person
{
public string FirstName { get; set; }
}
static class Program
{
static void Main(string[] args)
{
string name = "Michael";
Expression<Func<Person, object>> exp = p => p.FirstName == name;
new Visitor().Visit(exp);
}
}
class Visitor : ExpressionVisitor
{
protected override Expression VisitMember
(MemberExpression member)
{
if (member.Expression is ConstantExpression &&
member.Member is FieldInfo)
{
object container =
((ConstantExpression)member.Expression).Value;
object value = ((FieldInfo)member.Member).GetValue(container);
Console.WriteLine("Got value: {0}", value);
}
return base.VisitMember(member);
}
}
编辑:好的,稍微涉及访问者类的版本:
class Visitor : ExpressionVisitor
{
protected override Expression VisitMember
(MemberExpression memberExpression)
{
// Recurse down to see if we can simplify...
var expression = Visit(memberExpression.Expression);
// If we've ended up with a constant, and it's a property or a field,
// we can simplify ourselves to a constant
if (expression is ConstantExpression)
{
object container = ((ConstantExpression) expression).Value;
var member = memberExpression.Member;
if (member is FieldInfo)
{
object value = ((FieldInfo)member).GetValue(container);
return Expression.Constant(value);
}
if (member is PropertyInfo)
{
object value = ((PropertyInfo)member).GetValue(container, null);
return Expression.Constant(value);
}
}
return base.VisitMember(memberExpression);
}
}
现在运行:
var localPerson = new Person { FirstName = "Jon" };
Expression<Func<Person, object>> exp = p => p.FirstName == localPerson.FirstName;
Console.WriteLine("Before: {0}", exp);
Console.WriteLine("After: {0}", new Visitor().Visit(exp));
给出结果:
Before: p => Convert((p.FirstName ==
value(Program+<>c__DisplayClass1).localPerson.FirstName))
After: p => Convert((p.FirstName == "Jon"))
答案 1 :(得分:16)
以下是我为你列出的两种情况解决的问题。
基本上假设'=='的右侧可以被视为不带参数并返回值的函数,它可以编译为C#委托并调用以检索此值而不必担心究竟是什么右边的代码确实如此。
所以基本的示例代码在
之下class Visitor : ExpressionVisitor {
protected override Expression VisitBinary( BinaryExpression node ) {
var memberLeft = node.Left as MemberExpression;
if ( memberLeft != null && memberLeft.Expression is ParameterExpression ) {
var f = Expression.Lambda( node.Right ).Compile();
var value = f.DynamicInvoke();
}
return base.VisitBinary( node );
}
}
它查找二进制op寻找“arg.member == something”然后只编译/评估右侧,对于这两个示例,您提供的结果是字符串“Michael”。
请注意,如果您的右侧涉及使用lamda参数(如
),则会失败p.FirstName == CallSomeFunc(p.FirstName)
答案 2 :(得分:2)
一般情况下,您需要使用覆盖VisitConstant和VisitMember来实现自己的ExpressionVisitor,我们还需要一个用于MemberAccess节点的堆栈。
这种情况需要循环
var a = new { new b { c = true; } }
var expression = () => a.b.c;
这是访问常量方法的一部分
protected override Expression VisitConstant(ConstantExpression node)
{
MemberExpression prevNode;
var val = node.Value;
while ((prevNode = PreviousNode as MemberExpression) != null)
{
var fieldInfo = prevNode.Member as FieldInfo;
var propertyInfo = prevNode.Member as PropertyInfo;
if (fieldInfo != null)
val = fieldInfo.GetValue(val);
if (propertyInfo != null)
val = propertyInfo.GetValue(val);
Nodes.Pop();
}
// we got the value
// now val = constant we was looking for
return node;
}
PreviousNode是执行Stack.Peek
的属性答案 3 :(得分:0)
好的,这看起来非常有趣。显然,正在发生的事情是C#将本地堆栈作为常量对象传递,作为表达式的参数。如果你在你得到的表达式之上添加另一个表达式,比如fx。:
var count = 18;
Expression<Func<Person, object>> expr2 = p => p.FirstName == name && count > 10;
然后你的方法将停止工作 - “name”字段将不再是奇怪的“local-variables”对象中的第一个字段。
我不知道表达式的行为是这样的,但似乎你必须使用constantexpression来寻找MemberExpression,因为它是内部表达式。然后,您可以通过评估该表达式来获取值:
protected override Expression VisitMember(MemberExpression node) {
if (node.Expression.NodeType == ExpressionType.Constant) {
var inner = (ConstantExpression)node.Expression;
var value = (node.Member as FieldInfo).GetValue(inner.Value);
}
return base.VisitMember(node);
}
我不知道这是多么可靠,你可能需要更深入地检查元素表达,但是在这里显示的简单示例中,上面的内容将起作用。
答案 4 :(得分:0)
ConstantExpression的问题是编译器put使用私有匿名类的对象来存储lambda被关闭的值,因此常量的值是这个私有类的对象的值。要访问“实际”常量,您必须分析在之前出现表达式的表达式。过度简化的解决方案可能如下所示:
public sealed class ConstantValueExtractor : ExpressionVisitor
{
public static object ExtractFirstConstant(Expression expression)
{
var visitor = new ConstantValueExtractor();
visitor.Visit(expression);
return visitor.ConstantValue;
}
private ConstantValueExtractor()
{
}
private object ConstantValue
{
get;
set;
}
#region ExpressionVisitor Members
public override Expression Visit(Expression node)
{
this.pathToValue.Push(node);
var result = base.Visit(node);
this.pathToValue.Pop();
return result;
}
protected override Expression VisitConstant(ConstantExpression node)
{
// The first expression in the path is a ConstantExpression node itself, so just skip it.
var parentExpression = this.pathToValue.FirstOrDefault(
expression => expression.NodeType == ExpressionType.MemberAccess);
if (parentExpression != null)
{
// You might get notable performance overhead here, so consider caching
// compiled lambda or use other to extract the value.
var valueProviderExpression = Expression.Lambda>(
Expression.Convert(parentExpression, typeof(object)));
var valueProvider = valueProviderExpression.Compile();
this.ConstantValue = valueProvider();
}
return base.VisitConstant(node);
}
#endregion
#region private fields
private Stack pathToValue = new Stack();
#endregion
}
class Test
{
static void Main()
{
string name = "Michael";
Expression> exp = p => p.FirstName == name;
var value = ConstantValueExtractor.ExtractFirstConstant(exp);
Console.WriteLine(value);
}
}
我怀疑它是否适用于复杂的表达式,但你应该知道它是如何完成的。