C# 转换.net函数<;T>;到.net表达式<;Func<;T>&燃气轮机;

C# 转换.net函数<;T>;到.net表达式<;Func<;T>&燃气轮机;,c#,.net,lambda,expression,func,C#,.net,Lambda,Expression,Func,使用方法调用从lambda转换为表达式很容易 public void GimmeExpression(Expression<Func<T>> expression) { ((MemberExpression)expression.Body).Member.Name; // "DoStuff" } public void SomewhereElse() { GimmeExpression(() => thing.DoStuff()); } publ

使用方法调用从lambda转换为表达式很容易

public void GimmeExpression(Expression<Func<T>> expression)
{
    ((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}

public void SomewhereElse()
{
    GimmeExpression(() => thing.DoStuff());
}
public void GimmeExpression(表达式)
{
((MemberExpression)expression.Body).Member.Name;/“DoStuff”
}
公共空间在别处无效()
{
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());
}
public void ContainTheDanger(Func-dangerousCall)
{
尝试
{
危险呼叫();
}
捕获(例外e)
{
//下一行行不通。。。
表达式危险表达式=危险调用;
变量nameOfDanger=
((MemberExpression)dangerousCall.Body).Member.Name;
抛出新的危险容器(
“危险出现时”+危险名称,e);
}
}
公共空间在别处无效()
{
包含危险(()=>thing.CrossTheStreams());
}

不起作用的行给出了编译时错误
无法将类型“System.Func”隐式转换为“System.Linq.Expressions.Expression”
。显式强制转换不能解决这种情况。有没有我可以忽略的设施来做这件事?

哦,这一点都不容易
Func
表示泛型
委托
,而不是表达式。如果有任何方法可以做到这一点(由于编译器进行了优化和其他工作,一些数据可能会被丢弃,因此可能无法恢复原始表达式),那么就需要动态分解IL并推断表达式(这绝非易事)。将lambda表达式视为数据(
表达式
)是编译器完成的一个魔术(基本上,编译器在代码中构建表达式树,而不是将其编译为IL)

相关事实
这就是为什么将lambda推向极端的语言(如Lisp)通常更容易作为解释器实现的原因。在这些语言中,代码和数据本质上是相同的(即使在运行时),但我们的芯片无法理解这种形式的代码,因此我们必须通过在机器上构建一个能够理解它的解释器(类似Lisp语言的选择)或牺牲能力(代码将不再完全等于数据)来模拟这种机器在某种程度上(C#所做的选择)。在C#中,编译器通过允许lambda在编译时被解释为code
Func
)和数据(
Expression
),给人一种将代码视为数据的错觉。

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

  • 有不同的方法(每个方法1个)
  • 始终接受
    表达式
    版本,如果需要委托,只需
    .Compile().Invoke(…)
    即可。显然,这是有代价的

    • 您可能应该做的是改变方法。接受表达式>,然后编译并运行。如果失败,那么您已经有了要研究的表达式

      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());
      }
      
      public void ContainTheDanger(表达式危险呼叫)
      {
      尝试
      {
      危险调用().Compile().Invoke();;
      }
      捕获(例外e)
      {
      //下一行行不通。。。
      变量nameOfDanger=
      ((MemberExpression)dangerousCall.Body).Member.Name;
      抛出新的危险容器(
      “危险出现时”+危险名称,e);
      }
      }
      公共空间在别处无效()
      {
      包含危险(()=>thing.CrossTheStreams());
      }
      

      显然,你需要考虑这一点的性能含义,并确定它是否是你真正需要做的事情。

      < p>你可以通过.cCulielor()方法走另一条路——但不确定这是否对你有用:

      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());
      }
      
      public void ContainTheDanger(表达式危险呼叫)
      {
      尝试
      {
      var expr=dangerousCall.Compile();
      expr.Invoke();
      }
      捕获(例外e)
      {
      表达式危险表达式=危险调用;
      var nameOfDanger=((MethodCallExpression)dangerousCall.Body).Method.Name;
      抛出新的危险容器(“危险显现时”+危险名称,e);
      }
      }
      公共空间在别处无效()
      {
      var thing=新事物();
      包含危险(()=>thing.CrossTheStreams());
      }
      
      Cecil Mono团队的JB Evain正在为此做一些工作

      私有静态表达式FuncToExpression(Func f)
      {  
      返回x=>f(x);
      } 
      
      是一个将委托转换为表达式的库

      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)
          } 
      }
      
      公共类程序
      {
      私有静态void Main(字符串[]args){
      var lambda=lambda.TransformMethodTo()
      .From(()=>Parse)
      .ToLambda();
      }   
      公共静态int解析(字符串值){
      返回int.Parse(值)
      } 
      }
      
      表达式到表达式(Func调用)
      {
      MethodCallExpression methodCall=call.Target==null
      ?表达式调用(调用方法)
      :Expression.Call(Expression.Constant(Call.Target)、Call.Method);
      返回表达式.Lambda(methodCall);
      }
      
      更改

      // This next line does not work...
      Expression<Func<T>> DangerousExpression = dangerousCall;
      
      //下一行行不通。。。
      表达式危险表达式=危险调用;
      

      //下一行行可以用!
      表达式危险表达式=()=>危险调用();
      
      我想遍历返回表达式的语法树。这种方法允许我这样做吗?@DaveCameron-不。请参见上面的答案-已编译的
      Func
      将隐藏在新表达式中。这只是在代码上添加了一层数据;您可以遍历一个层,只需找到参数
      f
      ,而无需进一步的详细信息,因此您就可以从这里开始了。宏必须在编译时展开,如果您想支持
      eval
       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();