Expression\n\n想象一下:你正在编写 C# 代码。需要将一个过滤条件传递给一个方法。该选择什么:Func<User, bool> 还是 Expression<Func<User, bool>>?看起来差别很小——两者都接收一个用户并返回一个 bool。但在幕后,却隐藏着一个充满差异的宇宙。
\n\n在 Stack Overflow 上,这个问题获得了数千次浏览——这并非没有原因。今天我们将弄清楚为什么 Expression<Func<T>> 可能比普通的 Func<T> 更强大、更快速、更安全。最重要的是——何时使用它们,以免搬起石头砸自己的脚。
\n\n快速入门:什么是 Func 和 Expression?
\n\nFunc<T> 只是一个委托。一个准备好执行的函数,编译后的机器码。你向它传递数据——它处理这些数据。快速、高效,但……不透明。
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // 立即执行
\n\nExpression<Func<T>> 是一个表达式树。不是代码,而是代码的描述。你不能直接调用它——要么需要将其编译成委托(通过 .Compile()),要么需要分析其结构。
\n\nExpression<Func<User, bool>> expr = user => user.Age >= 18;\n// 不能直接调用:expr(someUser) —— 编译错误\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // 现在可以了
\n\n看起来,何必多此一举?但正是这种“描述性”打开了通往魔法的大门。
\n\n场景 1:LINQ to Objects(内存中操作)
\n\n当你处理内存中的集合时(例如 List<T>),LINQ to Objects 使用 Func<T>。这里不需要表达式树——所有操作都在同一进程中执行。
\n\nvar users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>
\n\n性能极佳:没有解析表达式树的开销。如果你只处理本地数据——放心使用 Func。它更快、更简单。
\n\n场景 2:LINQ to SQL / Entity Framework(数据库操作)
\n\n而这里,魔法开始了。当你编写:
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();
\n\nEntity Framework 不会执行你的 C# 代码。相反,它会分析传递的 表达式树 并将其转换为 SQL 查询:
\n\nSELECT * FROM Users WHERE Age >= 18
\n\n如果你传递了 Func,EF 将无法“窥视内部”——它只会从表中加载 所有 用户并在内存中执行过滤。想象一下:一张包含 1000 万条记录的表——所有这些记录都通过网络传输到你的应用程序,以便在客户端进行过滤。
\n\nExpression<Func<T>> 允许 EF(或任何其他 IQueryable 提供程序)读取你的条件,将其翻译成数据库语言(SQL、MongoDB 查询、OData 等)并在服务器端执行过滤。
\n\n性能:基准测试与实际数据
\n\n让我们看看具体数字。举一个简单的例子:按年龄过滤 100,000 个用户。
\n\n// 选项 1:Func —— 加载所有用户,在内存中过滤\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// 选项 2:Expression —— 在服务器端过滤\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18
\n\n结果(近似值,但具有代表性):
\n\n
想象一下:你正在编写 C# 代码。需要将一个过滤条件传递给一个方法。该选择什么:Func<User, bool> 还是 Expression<Func<User, bool>>?看起来差别很小——两者都接收一个用户并返回一个 bool。但在幕后,却隐藏着一个充满差异的宇宙。
在 Stack Overflow 上,这个问题获得了数千次浏览——这并非没有原因。今天我们将弄清楚为什么 Expression<Func<T>> 可能比普通的 Func<T> 更强大、更快速、更安全。最重要的是——何时使用它们,以免搬起石头砸自己的脚。
\n\n快速入门:什么是 Func 和 Expression?
\n\nFunc<T> 只是一个委托。一个准备好执行的函数,编译后的机器码。你向它传递数据——它处理这些数据。快速、高效,但……不透明。
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // 立即执行\n\nExpression<Func<T>> 是一个表达式树。不是代码,而是代码的描述。你不能直接调用它——要么需要将其编译成委托(通过 .Compile()),要么需要分析其结构。
Expression<Func<User, bool>> expr = user => user.Age >= 18;\n// 不能直接调用:expr(someUser) —— 编译错误\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // 现在可以了\n\n看起来,何必多此一举?但正是这种“描述性”打开了通往魔法的大门。
\n\n场景 1:LINQ to Objects(内存中操作)
\n\n当你处理内存中的集合时(例如 List<T>),LINQ to Objects 使用 Func<T>。这里不需要表达式树——所有操作都在同一进程中执行。
var users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>\n\n性能极佳:没有解析表达式树的开销。如果你只处理本地数据——放心使用 Func。它更快、更简单。
\n\n场景 2:LINQ to SQL / Entity Framework(数据库操作)
\n\n而这里,魔法开始了。当你编写:
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();\n\nEntity Framework 不会执行你的 C# 代码。相反,它会分析传递的 表达式树 并将其转换为 SQL 查询:
\n\nSELECT * FROM Users WHERE Age >= 18\n\n如果你传递了 Func,EF 将无法“窥视内部”——它只会从表中加载 所有 用户并在内存中执行过滤。想象一下:一张包含 1000 万条记录的表——所有这些记录都通过网络传输到你的应用程序,以便在客户端进行过滤。
\n\nExpression<Func<T>> 允许 EF(或任何其他 IQueryable 提供程序)读取你的条件,将其翻译成数据库语言(SQL、MongoDB 查询、OData 等)并在服务器端执行过滤。
\n\n性能:基准测试与实际数据
\n\n让我们看看具体数字。举一个简单的例子:按年龄过滤 100,000 个用户。
\n\n// 选项 1:Func —— 加载所有用户,在内存中过滤\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// 选项 2:Expression —— 在服务器端过滤\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18\n\n结果(近似值,但具有代表性):
\n- \n