【问题标题】:converting a .net Func<T> to a .net Expression<Func<T>>将 .net Func<T> 转换为 .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] 中的答案:stackoverflow.com/questions/9377635/create-expression-from-func/…

标签: c# .net lambda expression 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 构建它的语法糖。为什么你认为它应该在运行时失败?
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多