将.net Func < T > 转换为.net Expression < Func < T >

从 lambda 到 Expression 很容易使用方法调用..。

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>>'。显式强制转换不能解决这种情况。有什么我没注意到的设施吗?

78736 次浏览

一点都不容易。Func<T>表示泛型 delegate,而不是表达式。如果有任何方法可以做到这一点(由于优化和编译器所做的其他事情,一些数据可能会被丢弃,所以可能不可能得到原始的表达式) ,它将动态拆解 IL 并推断表达式(这绝非易事)。将 lambda 表达式视为数据(Expression<Func<T>>)是由 编译器完成的魔术(基本上编译器在代码中构建表达式树,而不是将其编译为 IL)。

相关事实

这就是为什么将 lambdas 推到极致的语言(比如 Lisp)通常更容易实现为 翻译。在这些语言中,代码和数据本质上是一样的(甚至在 运行时间中也是如此) ,但是我们的芯片无法理解这种形式的代码,所以我们必须仿效这样一台机器,在它的基础上建立一个能够理解它的解释器(像 Lisp 这样的语言所做的选择) ,或者在某种程度上牺牲功率(代码将不再完全等于数据)(C # 所做的选择)。在 C # 中,编译器通过允许在 编译时中将 lambdas 解释为 密码(Func<T>)和 资料(Expression<Func<T>>) ,给出了将代码视为数据的错觉。

如果你有时需要一个表达式,有时需要一个委托,你有两个选择:

  • 有不同的方法(每种方法一个)
  • 总是接受 Expression<...>版本,只有 .Compile().Invoke(...)它,如果你想要一个委托。显然这是有成本的。

你可能应该做的是,改变这个方法。接受一个 Expression > ,然后编译并运行。如果失败,您已经有了要查看的表达式。

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());
}

显然,您需要考虑这样做对性能的影响,并确定是否真的需要这样做。

然而,你可以通过.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());
}

来自 Cecil Mono 团队的 JB Evain 正在为实现这一点做一些进展

Http://evain.net/blog/articles/2009/04/22/converting-delegates-to-expression-trees

    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)
{
return x => f(x);
}

NJect.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)
}
}
 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);
}

改变

// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();