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


118

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


对于“稀有案例”示例,我的使用并不多。呼叫者正在传递Func <T>。无需重复给调用方Func <T>的内容(通过异常)。
亚当·拉尔夫

2
异常不在调用方中处理。而且,由于有多个呼叫站点传递不同的Func <T>,因此在呼叫者中捕获异常会产生重复。
戴夫·卡梅伦

1
异常堆栈跟踪旨在显示此信息。如果在Func <T>的调用中引发了异常,则它将显示在堆栈跟踪中。顺便说一句,如果您选择另一种方式,即接受一个表达式并对其进行编译以进行调用,则由于堆栈跟踪将显示类似于at lambda_method(Closure )已编译委托的调用,因此您将丢失该表达式。
亚当·拉尔夫

Answers:


104

哦,这根本不容易。Func<T>表示通用delegate而不是表达式。如果有任何方法可以做到(由于优化和编译器完成的其他工作,某些数据可能会被丢弃,因此可能无法取回原始表达式),这将是在即时拆卸IL的过程。并推断表达式(绝非易事)。将lambda表达式视为data(Expression<Func<T>>)是编译器的一项神奇功能(基本上,编译器在代码中构建表达式树,而不是将其编译为IL)。

相关事实

这就是为什么将lambda推到极限的语言(例如Lisp)通常更易于实现为解释器。在这些语言中,代码和数据本质上是同一件事(即使在运行时也是如此),但是我们的芯片无法理解这种形式的代码,因此我们必须通过在可以理解该机器的基础上构建一个解释器来模拟这样的机器(像语言一样由Lisp做出的选择)或在某种程度上牺牲了功能(代码将不再完全等于数据)(C#做出的选择)。在C#中,编译器通过允许在编译时将lambda解释为codeFunc<T>)和dataExpression<Func<T>>)来给人一种将代码视为数据的错觉。


3
Lisp不必解释,可以轻松编译。宏必须在编译时进行扩展,并且如果要支持eval,则需要启动编译器,但是除此之外,这样做完全没有问题。
配置器

2
“ Expression <Func <T >> DangerousExpression =()=> angerousCall();” 不简单?
mheyman 2014年

10
@mheyman这将为Expression您的包装器操作创建新的内容,但是将没有有关dangerousCall委托内部的表达式树信息。
Nenad 2014年

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

1
我想遍历返回表达式的语法树。这种方法能让我做到吗?
戴夫·卡梅伦

6
@DaveCameron-否。请参见上面的答案-已编译的内容Func将隐藏在新的Expression中。这只是在代码上增加了一层数据。您可以遍历一层来查找参数f而无需进一步的详细信息,因此您就从正确的位置开始。
Jonno 2012年

21

您可能应该做的就是扭转这种方法。接受一个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());
}

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


7

您可以通过.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());
}

6

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

  • 有不同的方法(每一种)
  • 始终接受Expression<...>版本,.Compile().Invoke(...)如果您需要委托,则仅接受版本。显然,这是有代价的。

6

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

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

您能否详细说明“这将不起作用”部分?您是否真的尝试过编译和执行它?还是它不适用于您的应用程序?
Dmitry Dzygin

1
FWIW,这可能不是主要门票的内容,但这是我所需要的。call.Target那是杀死我的部分。它工作了好几年,然后突然停止工作,开始抱怨静态/非静态等等。不管怎样,谢谢!
Eli Gassert


-1

更改

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

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

务实,这是获得表达的绝对合法方式。通过expression.lambda和expression.call来构建它的语法糖。您为什么认为它应该在运行时失败?
Roman Pokrovskij
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.