如何获取使用局部变量的ConstantExpression的值
本文关键字:局部变量 ConstantExpression 的值 何获取 获取 | 更新日期: 2023-09-27 18:05:13
我创建了一个覆盖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;
编辑:好的,感谢AHM的评论,现在你的意思更清楚了。
基本上,代码被编译为在一个单独的类中捕获name
,然后应用字段访问从引用它的实例的常量表达式中获取它的值。(它必须这样做,因为您可以在创建表达式后更改name
的值-但是表达式捕获变量,而不是值。)
所以你实际上不想在VisitConstant
的ConstantExpression
上做任何事情-你想在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"))
对于您列出的两种情况,我是这样解决的:
基本上假设右侧的'=='可以被视为不带参数并返回值的函数,它可以被编译为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 );
}
}
查找查找"arg"的二进制操作符。member == something"然后只编译/计算右边,对于两个示例,您提供的结果都是字符串"Michael"。
注意,如果你的右手边使用了像
这样的lambda参数,这就失败了p。FirstName == CallSomeFunc(p.FirstName)
一般来说,你需要实现自己的ExpressionVisitor与覆盖的VisitConstant和VisitMember,我们还需要一个堆栈的MemberAccess节点。
- 中的VisitMember将节点放到堆栈
- 在VisitConstant中创建一个'while循环'来分析前一个节点是否为MemberExpression:
- 获取前一个节点的Member属性
- 检测是否为FieldInfo或PropertyInfo
- 调用字段/属性信息的GetValue -它将是您需要的常量值或中间成员的值,可以用于在复杂情况下获得下一个值(见下文)
- 从栈中删除MemberExpression
- 闭环
在以下情况下需要循环
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
ConstantExpression的问题是编译器put's使用私有匿名类的对象来存储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);
}
}
之前我怀疑它是否能处理足够复杂的表达式,但你应该知道它是如何做到的。
好的,这看起来很有趣。显然,正在发生的事情是c#将本地stackframe作为常量对象传递给表达式的参数。如果你在你得到的表达式上面添加另一个表达式,比如fx.:
var count = 18;
Expression<Func<Person, object>> expr2 = p => p.FirstName == name && count > 10;
那么你的方法将停止工作- "name"字段将不再是奇怪的"local-variables"对象的第一个字段。
我不知道表达式的行为是这样的,但似乎你必须寻找在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);
}
我不知道这有多可靠,您可能需要更深入地检查成员表达式,但在这里展示的简化示例中,上面的方法可以工作。
这里是另一个没有DynamicInvoke
的解决方案因为树是递归遍历的,所以不需要堆栈。如果访问常量或静态方法的成员,则在遍历子节点后计算该值。
替换像
这样的表达式var myFoo = new Foo{ OtherObjects = new List<OtherObjec>{ new OtherObject {Prop = 5 }};
var expression = () => myFoo.OtherObjects[0].Prop
--->
var expression = () => 5
这个方法是不完整的,并不能处理所有复杂的情况,但它可以是一个很好的开始,为其他寻找解决这个问题的方法
public class ReplaceConstantVisitor : ExpressionVisitor
{
private int m_memberAccessDepth = 0;
private object m_constantValue;
private bool m_constantOnStack = false;
public ReplaceConstantVisitor()
{
m_constantValue = null;
}
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
var result = base.VisitMethodCall(methodCallExpression);
if (m_constantOnStack)
{
var reducedArguments = methodCallExpression.Arguments.OfType<ConstantExpression>().ToList();
if (reducedArguments.Count() == methodCallExpression.Arguments.Count)
{
m_constantValue = methodCallExpression.Method.Invoke(m_constantValue, reducedArguments.Select(x => x.Value).ToArray());
}
else
{
m_constantOnStack = false;
}
}
return result;
}
protected override Expression VisitMember(MemberExpression memberExpression)
{
m_memberAccessDepth++;
var result = base.VisitMember(memberExpression);
m_memberAccessDepth--;
// initial condition to do replacement
switch (memberExpression.Expression)
{
// replace constant member access
case ConstantExpression constantExpression:
m_constantOnStack = true;
m_constantValue = constantExpression.Value;
break;
// replace static member access
case null when memberExpression.Member.IsStatic():
m_constantOnStack = true;
break;
}
if (m_constantOnStack)
{
switch (memberExpression.Member)
{
case PropertyInfo propertyInfo:
m_constantValue = propertyInfo.GetValue(m_constantValue);
break;
case FieldInfo fieldInfo:
m_constantValue = fieldInfo.GetValue(m_constantValue);
break;
default:
m_constantOnStack = false; // error case abort replacement
break;
}
}
if (m_constantOnStack && m_memberAccessDepth == 0)
{
m_constantOnStack = false;
var constant = m_constantValue;
m_constantValue = null;
return Expression.Constant(constant);
}
return result;
}
}