C# 检查表达式是否有自由参数
作为funcletizer的一部分,我希望将不包含参数的c#表达式替换为其计算常量:C# 检查表达式是否有自由参数,c#,lambda,expression,func,C#,Lambda,Expression,Func,作为funcletizer的一部分,我希望将不包含参数的c#表达式替换为其计算常量: double d = 100.0; Expression<Func<double, double>> ex1 = x => -x; Expression<Func<double>> ex2 = () => -d; Expression result; result = Funcletize(ex1); // should return ex1 un
double d = 100.0;
Expression<Func<double, double>> ex1 = x => -x;
Expression<Func<double>> ex2 = () => -d;
Expression result;
result = Funcletize(ex1); // should return ex1 unmodified
result = Funcletize(ex2); // should return Expression.Constant(-100.0)
当表达式包含未绑定的参数时,如上面的ex1
所示,这当然会失败,引发InvalidOperationException,因为我没有提供任何参数。
如何检查表达式是否包含此类参数
我当前的解决方案涉及一个try{}catch(invalidoperationexception),但这似乎是一种非常不雅观且容易出错的方式:
// this works; by catching InvalidOperationException
public static Expression Funcletize(Expression ex)
{
try
{
// Compile() will throw InvalidOperationException,
// if the expression contains unbound parameters
var lambda = Expression.Lambda(ex).Compile();
Object value = lambda.DynamicInvoke();
return Expression.Constant(value, ex.Type);
}
catch (InvalidOperationException)
{
return ex;
}
}
当然,大多数事情都是可能的。这里有两个不同的因素:
- 通过跟踪表达式中显示的参数,删除未使用的参数
- 评估和内联捕获的变量(强调:这是一个语义变化)——我们通过识别字段->[field->]……field->模式来实现这一点(尽管在某些情况下显示的代码实际上可能会执行误报)
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用System.Linq.Expressions;
运用系统反思;
静态类程序
{
静态void Main()
{
双d=100;
表达式ex1=x=>-x;
表达式ex2=()=>-d;
var result1=解包(ex1);/(x)=>-x
var result2=Demungify(ex2);/()=>-100
}
公共静态LambdaExpression Demungify(LambdaExpression ex)
{
var visitor=new Demungifier();
var newBody=访客访问(ex.Body);
var args=ex.Parameters.Where(visitor.WasSeen.ToArray();
var lambda=表达式.lambda(newBody,args);
如果(!args.Any()&&&!(lambda.Body是常量表达式))
{
//评估一下!
对象结果=lambda.Compile().DynamicInvoke();
lambda=Expression.lambda(Expression.Constant(result,newBody.Type));
}
返回lambda;
}
类分解器:ExpressionVisitor
{
只读HashSet参数=新HashSet();
已看到公共布尔值(ParameterExpression param)
{
返回参数.Contains(param);
}
受保护的重写表达式VisitParameter(ParameterExpression节点)
{
参数。添加(节点);
返回基本访问参数(节点);
}
受保护的重写表达式VisitMember(MemberExpression节点)
{
目标价值;
if(tryeevaluate(节点,输出值))
{
返回表达式.Constant(值,((FieldInfo)node.Member).FieldType);
}
返回base.VisitMember(节点);
}
布尔TryEvaluate(表达式,输出对象值)
{
if(表达式==null)
{
值=空;
返回true;
}
if(expression.NodeType==ExpressionType.Constant)
{
value=((ConstantExpression)表达式);
返回true;
}
//捕获的变量始终是字段,可能是字段中的字段
//最终以恒定的压力终止,这是捕获上下文
成员表达成员;
if(expression.NodeType==ExpressionType.MemberAccess
&&(member=(MemberExpression)表达式).member.MemberType==System.Reflection.MemberTypes.Field)
{
目标对象;
if(tryeevaluate(member.Expression,out target))
{
value=((FieldInfo)member.member.GetValue(目标);
返回true;
}
}
值=空;
返回false;
}
}
}
在这种情况下,您可以将表达式
强制转换为LambdaExpression
,并查看它是否有参数
public static Expression Funcletize(Expression ex)
{
var l = ex as LambdaExpression;
if (l != null && l.Parameters.Count == 0)
{
var lambda = Expression.Lambda(ex).Compile();
Object value = lambda.DynamicInvoke();
return Expression.Constant(value, ex.Type);
}
return ex;
}
将
-d
更改为-100
不是一个安全的操作-这会从根本上改变表达式的含义(和潜在输出)。是否确实要计算对象字段?房产呢?方法?但是:如果您只是想删除表达式,但保留-d
,则可以这样做…是的,我想将-d更改为-100。我知道这可能会改变输出,但这是有意的。所有属性、字段和方法结果都应替换为它们的值。这要求所有的方法和属性都必须严格无副作用。如果你愿意假设无副作用对这两个例子有效,我会在更完整的无参数情况下对其进行编辑,但不幸的是,我需要一些更一般的汉克斯!这比我预期的要稍微复杂一些:)将不得不检查我是否可以将ExpressionVisitor后置到NET3.5,或者只是将整个项目提升到4.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
static class Program
{
static void Main()
{
double d = 100;
Expression<Func<double, double>> ex1 = x => -x;
Expression<Func<double>> ex2 = () => -d;
var result1 = Demungify(ex1); // (x) => -x
var result2 = Demungify(ex2); // () => -100
}
public static LambdaExpression Demungify(LambdaExpression ex)
{
var visitor = new Demungifier();
var newBody = visitor.Visit(ex.Body);
var args = ex.Parameters.Where(visitor.WasSeen).ToArray();
var lambda = Expression.Lambda(newBody, args);
if (!args.Any() && !(lambda.Body is ConstantExpression))
{
// evaluate that!
object result = lambda.Compile().DynamicInvoke();
lambda = Expression.Lambda(Expression.Constant(result, newBody.Type));
}
return lambda;
}
class Demungifier : ExpressionVisitor
{
readonly HashSet<ParameterExpression> parameters = new HashSet<ParameterExpression>();
public bool WasSeen(ParameterExpression param)
{
return parameters.Contains(param);
}
protected override Expression VisitParameter(ParameterExpression node)
{
parameters.Add(node);
return base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
object value;
if(TryEvaluate(node, out value))
{
return Expression.Constant(value, ((FieldInfo)node.Member).FieldType);
}
return base.VisitMember(node);
}
bool TryEvaluate(Expression expression, out object value)
{
if(expression == null)
{
value = null;
return true;
}
if(expression.NodeType == ExpressionType.Constant)
{
value = ((ConstantExpression)expression).Value;
return true;
}
// captured variables are always fields, potentially of fields of fields
// eventually terminating in a ConstantExpression that is the capture-context
MemberExpression member;
if(expression.NodeType == ExpressionType.MemberAccess
&& (member= (MemberExpression)expression).Member.MemberType == System.Reflection.MemberTypes.Field)
{
object target;
if(TryEvaluate(member.Expression, out target))
{
value = ((FieldInfo)member.Member).GetValue(target);
return true;
}
}
value = null;
return false;
}
}
}
public static Expression Funcletize(Expression ex)
{
var l = ex as LambdaExpression;
if (l != null && l.Parameters.Count == 0)
{
var lambda = Expression.Lambda(ex).Compile();
Object value = lambda.DynamicInvoke();
return Expression.Constant(value, ex.Type);
}
return ex;
}