如何获取使用局部变量的ConstantExpression的值?
我创建了一个覆盖VisitConstant的ExpressionVisitor实现。 但是,当我创建一个利用局部变量的表达式时,我似乎无法获得变量的实际值。
public class Person { public string FirstName { get; set; } } string name = "Michael"; Expression<Func> exp = p => p.FirstName == name;
我如何在ConstantExpression中获取变量“name”的值? 我唯一能想到的是:
string fieldValue = value.GetType().GetFields().First().GetValue(value).ToString();
显然,这并不适合非常灵活….
稍微复杂的例子如下:
Person localPerson = new Person { FirstName = "Michael" }; Expression<Func> exp = p => p.FirstName == localPerson.FirstName;
以下是我为您列出的两种情况解决的问题。
基本上假设’==’的右侧可以被视为不带参数并返回值的函数,它可以编译为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)
编辑:好的,由于AHM的评论,你现在的意思更清楚了。
基本上,代码被编译为在单独的类中捕获name
– 然后应用字段访问以从引用它的实例的常量表达式获取其值。 (必须这样做,因为您可以在创建表达式后更改name
的值 – 但表达式捕获变量,而不是值。)
因此,您实际上并不想对VisitConstant
的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> 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> 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"))
通常,您需要使用覆盖VisitConstant和VisitMember来实现自己的ExpressionVisitor,我们还需要一个用于MemberAccess节点的堆栈。
- 在VisitMember中将节点放在堆栈上
- 在VisitConstant中创建一个’while循环’来分析前一个节点是否为MemberExpression:
- 获取上一个节点的Member属性
- 检测它是否是FieldInfo或PropertyInfo
- 调用字段/属性信息的GetValue – 它将是您需要的常量值或中间成员的值,可用于在复杂情况下获取下一个值(请参阅下面的内容)
- 从堆栈中删除MemberExpression
- 闭环
这样的情况需要循环
var a = new { new b { c = true; } } var expression = () => abc;
这是访问常量方法的一部分
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的属性
ConstantExpression的问题是编译器使用私有匿名类的对象来存储值lambda被关闭的值,因此常量的值是这个私有类的对象的值。 要访问“实际”常量,您必须分析在ConstantExpression 之前出现的表达式。 过度简化的解决方案可能如下所示:
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); } }
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); } }
我怀疑它是否适用于复杂的表达式,但你应该知道它是如何完成的。
好的,这看起来很有趣。 显然,正在发生的事情是C#将本地堆栈作为常量对象传递,作为表达式的参数。 如果你在你得到的表达式之上添加另一个表达式,比如fx:
var count = 18; Expression> 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); }
我不知道这是多么可靠,你可能需要更深入地检查元素表达,但是在这里展示的简单示例中,上述方法将起作用。