将 .net Func<T> 转换为 .net Expression<Func<T>>
Posted
技术标签:
【中文标题】将 .net Func<T> 转换为 .net Expression<Func<T>>【英文标题】:converting a .net Func<T> to a .net Expression<Func<T>> 【发布时间】:2010-10-20 13:04:06 【问题描述】:使用方法调用很容易从 lambda 转换为表达式...
public void GimmeExpression(Expression<Func<T>> expression)
((MemberExpression)expression.Body).Member.Name; // "DoStuff"
public void SomewhereElse()
GimmeExpression(() => thing.DoStuff());
但我想将 Func 转换为表达式,仅在极少数情况下...
public void ContainTheDanger(Func<T> dangerousCall)
try
dangerousCall();
catch (Exception e)
// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;
var nameOfDanger =
((MemberExpression)dangerousCall.Body).Member.Name;
throw new DangerContainer(
"Danger manifested while " + nameOfDanger, e);
public void SomewhereElse()
ContainTheDanger(() => thing.CrossTheStreams());
不起作用的行给了我编译时错误Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'
。显式强制转换并不能解决这种情况。有没有我忽略的设施可以做到这一点?
【问题讨论】:
我认为“罕见情况”示例并没有多大用处。调用者传入 Funcat lambda_method(Closure )
的内容来调用已编译的委托。哦,这根本不容易。 Func<T>
代表通用 delegate
而不是表达式。如果有任何方法可以这样做(由于编译器所做的优化和其他事情,一些数据可能会被丢弃,因此可能无法取回原始表达式),它会即时反汇编 IL并推断表达式(这绝非易事)。将 lambda 表达式视为数据 (Expression<Func<T>>
) 是 编译器 完成的一项魔法(基本上,编译器在代码中构建表达式树,而不是将其编译为 IL)。
相关事实
这就是为什么将 lambda 推到极致的语言(如 Lisp)通常更容易实现为 解释器。在那些语言中,代码和数据本质上是相同的(即使在运行时),但我们的芯片无法理解这种形式的代码,所以我们必须通过在上面构建解释器来模拟这样的机器理解它(类似 Lisp 的语言做出的选择)或在某种程度上牺牲了能力(代码将不再完全等于数据)(C# 做出的选择)。在 C# 中,编译器通过允许将 lambdas 解释为 code (Func<T>
) 和 data (Expression<Func<T>>
) 在 编译时间。
【讨论】:
Lisp 不必被解释,它可以很容易地被编译。宏必须在编译时进行扩展,如果要支持eval
,则需要启动编译器,但除此之外,这样做完全没有问题。
"表达式Expression
关于您的包装器操作,但它不会有关于dangerousCall
委托内部的表达式树信息。【参考方案2】:
private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)
return x => f(x);
【讨论】:
我想遍历返回表达式的语法树。这种方法可以让我这样做吗? @DaveCameron - 否。请参阅上面的答案 - 已编译的Func
将隐藏在新的表达式中。这只是在代码上添加了一层数据;您可以遍历一层只是为了找到您的参数f
而无需更多详细信息,因此您就在起点。
它会导致实体框架核心的客户端评估异常。【参考方案3】:
你可能应该做的,是改变方法。接受一个表达式>,然后编译并运行。如果失败,您已经有了可以查看的表达式。
public void ContainTheDanger(Expression<Func<T>> dangerousCall)
try
dangerousCall().Compile().Invoke();;
catch (Exception e)
// This next line does not work...
var nameOfDanger =
((MemberExpression)dangerousCall.Body).Member.Name;
throw new DangerContainer(
"Danger manifested while " + nameOfDanger, e);
public void SomewhereElse()
ContainTheDanger(() => thing.CrossTheStreams());
显然,您需要考虑这对性能的影响,并确定这是否是您真正需要做的事情。
【讨论】:
【参考方案4】:NJection.LambdaConverter 是一个将委托转换为表达式的库
public class Program
private static void Main(string[] args)
var lambda = Lambda.TransformMethodTo<Func<string, int>>()
.From(() => Parse)
.ToLambda();
public static int Parse(string value)
return int.Parse(value)
【讨论】:
【参考方案5】:如果您有时需要表达式,有时需要委托,您有两种选择:
有不同的方法(每种 1 种) 始终接受Expression<...>
版本,如果您需要委托,只需接受.Compile().Invoke(...)
。显然这是有代价的。
【讨论】:
【参考方案6】:但是,您可以通过 .Compile() 方法采用另一种方式 - 不确定这是否对您有用:
public void ContainTheDanger<T>(Expression<Func<T>> dangerousCall)
try
var expr = dangerousCall.Compile();
expr.Invoke();
catch (Exception e)
Expression<Func<T>> DangerousExpression = dangerousCall;
var nameOfDanger = ((MethodCallExpression)dangerousCall.Body).Method.Name;
throw new DangerContainer("Danger manifested while " + nameOfDanger, e);
public void SomewhereElse()
var thing = new Thing();
ContainTheDanger(() => thing.CrossTheStreams());
【讨论】:
【参考方案7】: Expression<Func<T>> ToExpression<T>(Func<T> call)
MethodCallExpression methodCall = call.Target == null
? Expression.Call(call.Method)
: Expression.Call(Expression.Constant(call.Target), call.Method);
return Expression.Lambda<Func<T>>(methodCall);
【讨论】:
你能详细说明“这不起作用”部分吗?您是否真的尝试过编译和执行它?或者它在您的应用程序中不起作用? FWIW,这可能不是主要门票的内容,但这是我需要的。是call.Target
部分杀死了我。它工作了多年,然后突然停止工作并开始抱怨静态/非静态等等。无论如何,谢谢!
多年来我一直在使用 Override 的答案,但我的表达式现在包含一个不起作用的 Span<char>
。这本质上是相同的,但适用于Span<char>
。【参考方案8】:
Cecil Mono 团队的 JB Evain 正在取得一些进展以实现这一目标
http://evain.net/blog/articles/2009/04/22/converting-delegates-to-expression-trees
【讨论】:
【参考方案9】:改变
// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;
到
// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();
【讨论】:
Servy,这是获取表达式的绝对合法方式。通过 expression.lambda 和 expression.call 构建它的语法糖。为什么你认为它应该在运行时失败?以上是关于将 .net Func<T> 转换为 .net Expression<Func<T>>的主要内容,如果未能解决你的问题,请参考以下文章
从 Expression<Func<T, bool>> 转换为字符串
Func<T> 如何隐式转换为 Expression<Func<T>>?