将 .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&lt;T&gt;' to 'System.Linq.Expressions.Expression&lt;System.Func&lt;T&gt;&gt;'。显式强制转换并不能解决这种情况。有没有我忽略的设施可以做到这一点?

【问题讨论】:

我认为“罕见情况”示例并没有多大用处。调用者传入 Func。无需向调用者重复 Func 是什么(通过异常)。 异常不在调用者中处理。而且,由于有多个调用站点在不同的 Func 中传递,因此在调用者中捕获异常会产生重复。 异常堆栈跟踪旨在显示此信息。如果在 Func 的调用中抛出异常,这将显示在堆栈跟踪中。顺便说一句,如果您选择另一种方式,即接受一个表达式并编译它以供调用,您将失去它,因为堆栈跟踪会显示类似 at lambda_method(Closure ) 的内容来调用已编译的委托。 我想你应该看看这个 [link][1] [1] 中的答案:***.com/questions/9377635/create-expression-from-func/… 【参考方案1】:

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

相关事实

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

【讨论】:

Lisp 不必被解释,它可以很容易地被编译。宏必须在编译时进行扩展,如果要支持eval,则需要启动编译器,但除此之外,这样做完全没有问题。 "表达式> DangerousExpression = () => dangerousCall();"不容易吗? @mheyman 这将创建新的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&lt;...&gt; 版本,如果您需要委托,只需接受.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&lt;char&gt;。这本质上是相同的,但适用于Span&lt;char&gt;【参考方案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>>?

C#如何将T的动作转换为T的任务的等待函数

将 Func 委托与 Async 方法一起使用

ActionAction<T>Func<T>Predicate<T>

C# 将 Func<T1, object> 转换为 Func<T1, T2>