133

使用方法调用很容易从 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>>'。显式强制转换并不能解决这种情况。有没有我忽略的设施可以做到这一点?

4

9 回答 9

112

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

相关事实

这就是为什么将 lambda 推到极致的语言(如 Lisp)通常更容易实现为解释器。在那些语言中,代码和数据本质上是相同的东西(即使在运行时),但是我们的芯片无法理解这种形式的代码,所以我们必须通过在它之上构建一个能够理解它的解释器来模拟这样的机器(类似语言的 Lisp 做出的选择)或在某种程度上牺牲了能力(代码将不再完全等于数据)(C# 做出的选择)。在 C# 中,编译器允许 lambda在编译时被解释为代码( Func<T>) 和数据( ),从而产生将代码视为数据的错觉。Expression<Func<T>>

于 2009-04-20T10:43:10.077 回答
43
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 
于 2011-02-27T13:48:59.180 回答
25

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

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

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

于 2009-04-20T10:53:59.567 回答
10

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)
    } 
}
于 2012-09-02T17:29:07.073 回答
8

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

  • 有不同的方法(每种1个)
  • 总是接受这个Expression<...>版本,.Compile().Invoke(...)如果你想要一个代表就接受它。显然,这是有代价的。
于 2009-04-20T10:52:58.383 回答
8

但是,您可以通过 .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());
}
于 2009-04-20T10:55:39.627 回答
6
 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);
        }
于 2013-03-21T12:38:54.810 回答
3

Cecil Mono 团队的 JB Evain 正在取得一些进展以实现这一目标

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

于 2009-05-01T19:38:41.277 回答
-1

改变

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

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();
于 2014-02-17T21:22:52.220 回答