首页 文章

为什么要使用Expression <Func <T >>而不是Func <T>?

提问于
浏览
812

我理解lambdas以及 FuncAction 代表 . 但表达方式让我感到困惑 . 在什么情况下你会使用 Expression<Func<T>> 而不是普通的 Func<T>

9 回答

  • 62

    当您想将lambda表达式视为表达式树并查看它们而不是执行它们时 . 例如,LINQ to SQL获取表达式并将其转换为等效的SQL语句并将其提交给服务器(而不是执行lambda) .

    从概念上讲, Expression<Func<T>>Func<T> 完全不同 . Func<T> 表示 delegate ,它几乎是指向方法的指针, Expression<Func<T>> 表示lambda表达式的树数据结构 . 这种树结构 describes what a lambda expression does 而不是做实际的事情 . 它基本上保存有关表达式,变量,方法调用的组合的数据......(例如它保存诸如此lambda之类的信息是一些常量的某些参数) . 您可以使用此描述将其转换为实际方法(使用 Expression.Compile )或使用它执行其他操作(如LINQ to SQL示例) . 将lambdas视为匿名方法和表达式树的行为纯粹是编译时间的事情 .

    Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
    

    将有效地编译为一个没有任何东西的IL方法并返回10 .

    Expression<Func<int>> myExpression = () => 10;
    

    将转换为描述不带参数的表达式并返回值10的数据结构:

    Expression vs Func

    虽然它们在编译时看起来都一样,但编译器生成的是 totally different .

  • 7

    我正在添加一个新的答案,因为这些答案似乎在我脑海中,直到我意识到它是多么简单 . 有时候你的期望很复杂,让你无法“绕过它” .

    我不需要理解差异,直到我走进一个非常讨厌的'bug'试图一般地使用LINQ-to-SQL:

    public IEnumerable<T> Get(Func<T, bool> conditionLambda){
      using(var db = new DbContext()){
        return db.Set<T>.Where(conditionLambda);
      }
    }
    

    这很有效,直到我开始在较大的数据集上获取OutofMemoryExceptions . 在lambda中设置断点让我意识到它在我的表中逐行迭代寻找与我的lambda条件的匹配 . 这困扰了我一段时间,因为为什么它将我的数据表视为一个巨大的IEnumerable而不是像它应该做的LINQ-to-SQL?它在我的LINQ-to-MongoDb版本中也做了完全相同的事情 .

    修复只是将 Func<T, bool> 变成了 Expression<Func<T, bool>> ,所以我用Google搜索为什么它需要一个 Expression 而不是 Func ,最后来到这里 .

    An expression simply turns a delegate into a data about itself. 所以 a => a + 1 变得像“在左侧有一个 int a . 在右侧你加1 . ” That's it. 你现在可以回家了 . 它基本上都是一个表达树真的 - 没有什么可以包裹你的头 .

    理解这一点,很明显为什么LINQ-to-SQL需要一个 Expression ,而 Func 是不够的 . Func 不知道是否在减法时进行加法或乘法 . 你所能做的只是运行它 . 另一方面, Expression 允许您查看委托内部并查看它想要执行的所有操作,使您能够将其转换为您想要的任何内容,例如SQL查询 . Func 无法正常工作,因为我的DbContext对lambda表达式实际上将其转换为SQL是盲目的,所以它做了下一个最好的事情,并通过我表中的每一行迭代条件 .

    编辑:在John Peter的要求下阐述我的最后一句话:

    IQueryable扩展了IEnumerable,因此IEnumerable的方法如 Where() 获得了接受 Expression 的重载 . 当你将 Expression 传递给它时,你会保留一个IQueryable,但是当你传递一个 Func 时,你会得到一个IEnumerable . 换句话说,没有注意到你很难注意到差异,直到你真正看到签名的引擎盖 .

  • 15

    在选择Expression vs Func时,一个非常重要的考虑因素是像LINQ to Entities这样的IQueryable提供者可以“消化”你在Expression中传递的内容,但会忽略你在Func中传递的内容 . 我有两个关于这个主题的博客文章:

    More on Expression vs Func with Entity FrameworkFalling in Love with LINQ - Part 7: Expressions and Funcs(最后一节)

  • 997

    我想补充一些关于 Func<T>Expression<Func<T>> 之间差异的说明:

    • Func<T> 只是一个普通的老式MulticastDelegate;

    • Expression<Func<T>> 是表达式树形式的lambda表达式的表示;

    • 表达式树可以通过lambda表达式语法或API语法构建;

    • 表达式树可以编译为委托 Func<T> ;

    • 逆转换在理论上是可行的,但它不是一个简单的过程;
      可以通过 ExpressionVisitor 观察/翻译/修改

    • 表达式树;

    • IEnumerable的扩展方法用 Func<T> 操作;

    • IQueryable的扩展方法使用 Expression<Func<T>> 进行操作 .

    有一篇文章用代码示例描述了细节:
    LINQ: Func<T> vs. Expression<Func<T>> .

    希望它会很有帮助 .

  • 18

    Krzysztof Cwalina的书(框架设计指南:可重用.NET库的约定,成语和模式)对此有更多的哲学解释;

    Edit for non-image version:

    大多数情况下,如果需要执行的只是运行一些代码,您将需要Func或Action . 在代码需要在运行之前进行分析,序列化或优化时,您需要Expression . 表达式用于思考代码,Func / Action用于运行代码 .

  • 94

    LINQ是规范的例子(例如,与数据库交谈),但事实上,任何时候你更关心表达要做什么,而不是实际做 . 例如,我在protobuf-net的RPC堆栈中使用此方法(以避免代码生成等) - 因此您使用以下方法调用方法:

    string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
    

    这解构表达式树以解析 SomeMethod (以及每个参数的值),执行RPC调用,更新任何 ref / out args,并从远程调用返回结果 . 这只能通过表达式树实现 . 我更多地介绍here .

    另一个例子是当你手动构建表达式树以便编译为lambda时,就像generic operators代码所做的那样 .

  • 53

    如果要将函数视为数据而不是代码,则可以使用表达式 . 如果要操作代码(作为数据),可以执行此操作 . 大多数情况下,如果您不需要表达式,那么您可能不需要使用表达式 .

  • 236

    主要原因是您不想直接运行代码,而是想要检查它 . 这可能有多种原因:

    • 将代码映射到不同的环境(即,实体框架中的C#代码到SQL)

    • 在运行时替换部分代码(动态编程甚至是纯DRY技术)

    • 代码验证(在模拟脚本或进行分析时非常有用)

    • 序列化 - 表达式可以相当容易和安全地序列化,代理人不能

    • 在运行时没有进行动态调用的事物的强类型安全性(使用Razor的ASP.NET MVC 5就是一个很好的例子)

  • 35

    我没有看到任何提及表现的答案 . 将 Func<> s传递给 Where()Count() 是不好的 . 真的很糟糕 . 如果你使用 Func<> 然后它调用 IEnumerable LINQ的东西而不是 IQueryable ,这意味着整个表被拉入然后被过滤 . Expression<Func<>> 明显更快,特别是如果您查询的是另一台服务器的数据库 .

相关问题