ChatGPT解决这个技术问题 Extra ChatGPT

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

我了解 lambda 以及 FuncAction 委托。但是表情难住了我。

在什么情况下您会使用 Expression<Func<T>> 而不是普通的旧 Func<T>

Func<> 会在c#编译器级别转换为方法,Expression> 直接编译代码后会在MSIL级别执行,这就是它更快的原因
除了答案,csharp语言规范“4.6表达式树类型”有助于交叉引用
对于希望与 C# 语言规范进行交叉引用的任何人:Expression Tree Types

m
mmx

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

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

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

将有效地编译成一个什么都得不到并返回 10 的 IL 方法。

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

将被转换为描述一个没有参数并返回值 10 的表达式的数据结构:

https://i.stack.imgur.com/gwU0E.jpg

虽然它们在编译时看起来相同,但编译器生成的完全不同。


因此,换句话说,Expression 包含有关某个委托的元信息。
@bertl 实际上,不。代表根本不参与。与委托有任何关联的原因是您可以将表达式 to 编译为委托 - 或者更准确地说,将其编译为方法并将该方法的委托作为返回值.但表达式树本身只是数据。当您使用 Expression<Func<...>> 而不仅仅是 Func<...> 时,委托不存在。
@Kyle Delaney (isAnExample) => { if(isAnExample) ok(); else expandAnswer(); } 这样的表达式是一个 ExpressionTree,为 If 语句创建分支。
@bertl Delegate 是 CPU 所看到的(一种架构的可执行代码),Expression 是编译器所看到的(只是另一种格式的源代码,但仍然是源代码)。
@bertl:可以更准确地概括为表达式之于 func 就像 stringbuilder 之于字符串。它不是一个字符串/函数,但它包含在被要求时创建一个所需的数据。
D
David DeLuca

我正在添加一个新手答案,因为这些答案似乎在我脑海中,直到我意识到它是多么简单。有时,您期望它很复杂,这使您无法“绕开它”。

直到我遇到一个非常烦人的“错误”,试图通用地使用 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,最后在这里结束。

表达式只是将委托转换为关于其自身的数据。因此 a => a + 1 变成类似于“在左侧有一个 int a。在右侧添加 1”。 就是这样。你现在可以回家了。它显然比这更有条理,但这实际上就是一个表达式树的全部内容——没有什么可以让你头疼的。

理解了这一点,就很清楚为什么 LINQ-to-SQL 需要 Expression,而 Func 是不够的。 Func 并没有提供一种深入了解自身的方法,无法了解如何将其转换为 SQL/MongoDb/其他查询的细节。你看不到它是在做加法还是乘法或减法。你所能做的就是运行它。另一方面,Expression 允许您查看委托内部并查看它想要执行的所有操作。这使您能够将委托转换为您想要的任何内容,例如 SQL 查询。 Func 不起作用,因为我的 DbContext 对 lambda 表达式的内容视而不见。因此,它无法将 lambda 表达式转换为 SQL;但是,它做了次好的事情,并通过我表中的每一行迭代了该条件。

编辑:应约翰彼得的要求解释我的最后一句话:

IQueryable 扩展了 IEnumerable,因此 IEnumerable 的方法(如 Where())获得接受 Expression 的重载。当您将 Expression 传递给它时,您会保留一个 IQueryable 作为结果,但是当您传递一个 Func 时,您将退回到基础 IEnumerable,因此您将获得一个 IEnumerable。换句话说,你没有注意到你已经把你的数据集变成了一个要迭代的列表,而不是要查询的东西。除非您真正深入了解签名,否则很难注意到差异。


乍得;请再解释一下这个评论:“Func 不起作用,因为我的 DbContext 对 lambda 表达式中的实际内容视而不见,无法将其转换为 SQL,因此它做了下一个最好的事情,并在我的表中的每一行中迭代该条件。”
>> Func... 你所能做的就是运行它。这并不完全正确,但我认为这是应该强调的一点。要运行功能/操作,要分析表达式(在运行之前甚至代替运行)。
@Chad这里的问题是什么?: db.Set 查询了所有数据库表,然后,因为 .Where(conditionLambda) 使用了 Where(IEnumerable) 扩展方法,该方法是枚举内存中的整个表.我认为您得到 OutOfMemoryException 是因为,此代码试图将整个表加载到内存中(当然还创建了对象)。我对吗?谢谢 :)
我认为@JohnPeters 问题的一个更简单的解释是,在幕后 LinqToSql 正在将您的 lambda 表达式从 Linq .Where(x => x.Value > 30) 转换为 Sql 字符串“WHERE Value > 30”并将其传递给数据库。 Expression 是让这种情况发生的神奇juju。
@bbqchickenrobot 类型很重要 - Compile() 的结果将是 Func<>,因此您将 Func<> 传递给 Find 方法 - 不涉及 Expression。所以你的表现会很糟糕。
L
LSpencer777

选择 Expression 与 Func 的一个极其重要的考虑因素是,像 LINQ to Entities 这样的 IQueryable 提供程序可以“消化”您在 Expression 中传递的内容,但会忽略您在 Func 中传递的内容。我有两篇关于这个主题的博客文章:

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


+l 用于解释。但是,我得到“LINQ to Entities 不支持 LINQ 表达式节点类型‘Invoke’。”并且在获取结果后必须使用 ForEach 。
O
Oğuzhan Soykan

Krzysztof Cwalina 的书(Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries)对此有更哲学的解释;

编辑非图像版本:

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


说得好。 IE。当您期望将 Func 转换为某种查询时,您需要表达式。 IE。您需要将 database.data.Where(i => i.Id > 0) 作为 SELECT FROM [data] WHERE [id] > 0 执行。如果你只是传入一个 Func,你已经在你的驱动程序上设置了盲点,它所能做的就是 SELECT *,然后一旦将所有数据加载到内存中,遍历每个并过滤掉所有 id > 0. 将 Func 包装在 Expression 中使驱动程序能够分析 Func 并将其转换为 Sql/MongoDb/other 查询。
因此,当我计划休假时,我会使用 Expression,但当我休假时,它将是 Func/Action ;)
@ChadHedgcock 这是我需要的最后一块。谢谢。我已经看了一段时间了,你在这里的评论让所有的研究都点击了。
O
Olexander Ivanitskyi

我想就 Func<T>Expression<Func<T>> 之间的区别添加一些注释:

Func 只是一个普通的老式 MulticastDelegate;

Expression> 是表达式树形式的 lambda 表达式的表示;

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

表达式树可以编译为委托 Func;

反向转换在理论上是可能的,但它是一种反编译,没有内置功能,因为它不是一个简单的过程;

可以通过 ExpressionVisitor 观察/翻译/修改表达式树;

IEnumerable 的扩展方法使用 Func 进行操作;

IQueryable 的扩展方法使用 Expression> 进行操作。

有一篇文章描述了代码示例的详细信息:
LINQ: Func<T> vs. Expression<Func<T>>

希望它会有所帮助。


不错的清单,一个小提示是您提到逆向转换是可能的,但是精确的逆向转换是不可能的。一些元数据在转换过程中丢失。但是,您可以将其反编译为表达式树,再次编译时会产生相同的结果。
P
PeterFett

LINQ 是典型的示例(例如,与数据库交谈),但事实上,任何时候您更关心表达 what 要做什么,而不是实际去做。例如,我在 protobuf-net 的 RPC 堆栈中使用这种方法(以避免代码生成等) - 所以你调用一个方法:

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

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

另一个示例是当您手动构建表达式树以编译为 lambda 时,如 generic operators 代码所做的那样。


A
Andrew Hare

当您想将函数视为数据而不是代码时,您将使用表达式。如果您想操作代码(作为数据),您可以这样做。大多数情况下,如果您不需要表达式,那么您可能不需要使用表达式。


L
Luaan

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

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

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

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

序列化 - 表达式可以相当容易和安全地序列化,委托不能

对本质上不是强类型的事物进行强类型安全,并利用编译器检查,即使您在运行时进行动态调用(带有 Razor 的 ASP.NET MVC 5 就是一个很好的例子)


你能详细说明一下 5 号吗
@uowzd01 看看 Razor - 它广泛使用这种方法。
@Luaan 我正在寻找表达式序列化,但在没有有限的第三方使用的情况下找不到任何东西。 .Net 4.5 是否支持表达式树序列化?
@vabii 不是我所知道的-对于一般情况来说,这并不是一个好主意。我的观点更多的是关于您能够针对您想要支持的特定情况编写非常简单的序列化,针对提前设计的接口——我已经做过几次了。在一般情况下,Expression 与委托一样不可能序列化,因为任何表达式都可以包含对任意委托/方法引用的调用。当然,“简单”是相对的。
I
Ian Kemp

使用 LINQ-to-SQL 时,将 Func<> 传递给 Where()Count() 是不好的。真糟糕。如果您使用 Func<>,那么它调用 IEnumerable LINQ 东西而不是 IQueryable,这意味着整个表被拉入并 然后 被过滤。 Expression<Func<>> 明显更快,因为它在SQL 服务器上执行过滤 - 特别是当您查询位于另一台服务器上的数据库时。


这是否也适用于内存查询?
@stt106 可能不会。
仅当您枚举列表时才适用。如果您使用 GetEnumerator 或 foreach,您将不会将 ienumerable 完全加载到内存中。
@stt106 当传递给 List<> 的 .Where() 子句时,表达式<Func<>>获取 .Compile() 调用它,所以 Func<>几乎可以肯定更快。请参阅referencesource.microsoft.com/#System.Core/System/Linq/…
D
Default

这里过于简化了,但 Func 是一台机器,而 Expression 是一个蓝图。 :D


X
XAMT

很高兴知道您可以将 Func<TEntity, bool>AsQueryable() 扩展方法(如 Expression<Func<TEntity, bool>>)一起使用。

Func<App, bool> filter = x => x.Alias.Contains("gan");
var query = dbSet.Where(filter).AsQueryable();

在您使用 Count()ToList() 等执行方法之前,不会执行查询。